您真正想要的是某种将异常传递给父进程的方法,对吧?然后你就可以随心所欲地处理它们了。
如果你使用concurrent.futures.ProcessPoolExecutor http://docs.python.org/dev/library/concurrent.futures.html,这是自动的。如果你使用multiprocessing.Pool http://docs.python.org/dev/library/multiprocessing.html#using-a-pool-of-workers,这是微不足道的。如果您使用显式Process
and Queue
,你必须做一些工作,但这不是that much.
例如:
def run(self):
try:
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put(result)
except Exception as e:
self.outputQueue.put(e)
然后,你的调用代码就可以读取Exception
像其他任何事情一样从队列中消失。而不是这个:
yield outq.pop()
do this:
result = outq.pop()
if isinstance(result, Exception):
raise result
yield result
(我不知道你的实际父进程队列读取代码是做什么的,因为你的最小样本只是忽略了队列。但希望这可以解释这个想法,即使你的真实代码实际上并不是这样工作的。)
这假设您想要中止任何未处理的异常,使其达到run
。如果你想传回异常并继续下一步i in iter
,只需移动try
进入for
,而不是围绕它。
这还假设Exception
s 不是有效值。如果这是一个问题,最简单的解决方案就是推动(result, exception)
tuples:
def run(self):
try:
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put((result, None))
except Exception as e:
self.outputQueue.put((None, e))
然后,您的弹出代码将执行以下操作:
result, exception = outq.pop()
if exception:
raise exception
yield result
您可能会注意到,这类似于 node.js 回调样式,您可以在其中传递(err, result)
每个回调。是的,这很烦人,而且你会弄乱这种风格的代码。但除了包装器之外,您实际上并没有在任何地方使用它;所有从队列中获取值或在内部调用的“应用程序级”代码run
只看到正常的回报/收益率和引发的异常。
您甚至可能想考虑建立一个Future
至规格concurrent.futures
(或按原样使用该类),即使您正在手动排队和执行工作。这并不难,而且它为您提供了一个非常好的 API,特别是对于调试而言。
最后,值得注意的是,大多数围绕工作线程和队列构建的代码都可以通过执行器/池设计变得更加简单,即使您绝对确定每个队列只需要一个工作线程。只需废弃所有样板,然后转动循环即可Worker.run
方法到函数中(这只是return
s or raise
s 正常,而不是附加到队列)。在调用方,再次废弃所有样板文件,然后submit
or map
工作功能及其参数。
您的整个示例可以简化为:
def job(i):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
return result
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
results = executor.map(job, range(10))
并且它会自动正确处理异常。
正如您在评论中提到的,异常的回溯不会回溯到子进程;而是回溯到子进程。它只限于手册raise result
调用(或者,如果您使用的是池或执行器,则调用池或执行器的内部)。
原因是multiprocessing.Queue
是建立在pickle
,并且腌制异常不会腌制它们的回溯。原因是你无法腌制回溯。原因是回溯充满了对本地执行上下文的引用,因此让它们在另一个进程中工作将非常困难。
那么……你能对此做些什么呢?不要去寻找完全通用的解决方案。相反,想想你真正需要什么。 90% 的情况下,您想要的是“记录异常,带回溯,然后继续”或“打印异常,带回溯,以stderr
and exit(1)
就像默认的未处理异常处理程序一样。对于其中任何一个,您根本不需要传递异常;只需在子端格式化它并传递一个字符串即可。如果您do需要更奇特的东西,准确地计算出您需要的东西,并传递足够的信息来手动将它们组合在一起。如果您不知道如何格式化回溯和异常,请参阅traceback http://docs.python.org/2/library/traceback.html模块。这很简单。这意味着您根本不需要进入泡菜机器。 (并不是说很难copyreg
一个 pickler 或编写一个持有者类__reduce__
方法或任何东西,但如果你不需要,为什么要学习所有这些?)