在调用线程中捕获线程的异常?
我刚开始学习Python和多线程编程。简单来说,我有一个脚本可以把文件复制到另一个地方。我想把这个操作放到一个单独的线程里,这样我就可以输出....
来表示脚本还在运行。
我遇到的问题是,如果文件无法复制,就会抛出一个异常。如果在主线程中运行,这没问题;但是下面的代码在这里就不行:
try:
threadClass = TheThread(param1, param2, etc.)
threadClass.start() ##### **Exception takes place here**
except:
print "Caught an exception"
在线程类里面,我尝试重新抛出这个异常,但没有成功。我看到这里有人问过类似的问题,但他们似乎都在做一些比我想做的更具体的事情(我也不太理解他们提供的解决方案)。我还看到有人提到使用sys.exc_info()
,但我不知道该在哪里或者怎么使用它。
编辑:下面是线程类的代码:
class TheThread(threading.Thread):
def __init__(self, sourceFolder, destFolder):
threading.Thread.__init__(self)
self.sourceFolder = sourceFolder
self.destFolder = destFolder
def run(self):
try:
shul.copytree(self.sourceFolder, self.destFolder)
except:
raise
22 个回答
concurrent.futures
模块让我们可以很简单地在不同的线程(或者进程)中做事情,同时处理可能出现的错误:
import concurrent.futures
import shutil
def copytree_with_dots(src_path, dst_path):
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
# Execute the copy on a separate thread,
# creating a future object to track progress.
future = executor.submit(shutil.copytree, src_path, dst_path)
while future.running():
# Print pretty dots here.
pass
# Return the value returned by shutil.copytree(), None.
# Raise any exceptions raised during the copy process.
return future.result()
concurrent.futures
是从Python 3.2开始就自带的,如果你用的是更早的版本,可以通过futures模块来使用类似的功能。
这个问题有很多复杂得让人摸不着头脑的回答。我是不是想得太简单了?因为对我来说,这样的处理方式似乎已经足够了。
from threading import Thread
class PropagatingThread(Thread):
def run(self):
self.exc = None
try:
if hasattr(self, '_Thread__target'):
# Thread uses name mangling prior to Python 3.
self.ret = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
else:
self.ret = self._target(*self._args, **self._kwargs)
except BaseException as e:
self.exc = e
def join(self, timeout=None):
super(PropagatingThread, self).join(timeout)
if self.exc:
raise self.exc
return self.ret
如果你确定只会在某一个版本的Python上运行,你可以把run()
方法简化成只有一种处理方式。如果你只在Python 3之前的版本上运行,就用混淆版本;如果你只在Python 3及以后的版本上运行,就用干净版本。
使用示例:
def f(*args, **kwargs):
print(args)
print(kwargs)
raise Exception('I suck at this')
t = PropagatingThread(target=f, args=(5,), kwargs={'hello':'world'})
t.start()
t.join()
当你合并线程时,你会看到在另一个线程中抛出的异常。
如果你使用six
库或者只在Python 3上工作,你可以改善当异常被重新抛出时获得的堆栈信息。这样,你不仅能看到合并时的堆栈信息,还可以把内部异常包裹在一个新的外部异常中,这样就能同时获得两个堆栈信息:
six.raise_from(RuntimeError('Exception in thread'),self.exc)
或者
raise RuntimeError('Exception in thread') from self.exc
问题在于,thread_obj.start()
这个命令执行后会立刻返回。你创建的子线程会在自己的环境中运行,拥有自己的调用栈。任何在子线程中发生的错误都是在子线程的环境里,并且是在它自己的调用栈中。现在我想到的一种方法是通过某种消息传递的方式,把这些信息传递给父线程,所以你可以考虑一下这个方法。
试试这个:
import sys
import threading
import queue
class ExcThread(threading.Thread):
def __init__(self, bucket):
threading.Thread.__init__(self)
self.bucket = bucket
def run(self):
try:
raise Exception('An error occured here.')
except Exception:
self.bucket.put(sys.exc_info())
def main():
bucket = queue.Queue()
thread_obj = ExcThread(bucket)
thread_obj.start()
while True:
try:
exc = bucket.get(block=False)
except queue.Empty:
pass
else:
exc_type, exc_obj, exc_trace = exc
# deal with the exception
print exc_type, exc_obj
print exc_trace
thread_obj.join(0.1)
if thread_obj.isAlive():
continue
else:
break
if __name__ == '__main__':
main()