Il semble que lorsqu'une exception est générée à partir d'un processus multitraitement.Pool, il n'y a aucune trace de pile ou autre indication de son échec. Exemple:
from multiprocessing import Pool
def go():
print(1)
raise Exception()
print(2)
p = Pool()
p.apply_async(go)
p.close()
p.join()
affiche 1 et s’arrête silencieusement. Fait intéressant, la levée d’une exception BaseException fonctionne. Est-il possible de rendre le comportement pour toutes les exceptions identique à BaseException?
J'ai une solution raisonnable pour le problème, au moins à des fins de débogage. Je n'ai pas actuellement de solution qui soulève l'exception dans les processus principaux. Ma première pensée a été d’utiliser un décorateur, mais vous ne pouvez décaper que les fonctions définies au niveau supérieur d’un module , c’est tout.
Au lieu de cela, une classe d'habillage simple et une sous-classe de pool qui l'utilise pour apply_async
(et donc apply
). Je laisserai map_async
comme exercice pour le lecteur.
import traceback
from multiprocessing.pool import Pool
import multiprocessing
# Shortcut to multiprocessing's logger
def error(msg, *args):
return multiprocessing.get_logger().error(msg, *args)
class LogExceptions(object):
def __init__(self, callable):
self.__callable = callable
def __call__(self, *args, **kwargs):
try:
result = self.__callable(*args, **kwargs)
except Exception as e:
# Here we add some debugging help. If multiprocessing's
# debugging is on, it will arrange to log the traceback
error(traceback.format_exc())
# Re-raise the original exception so the Pool worker can
# clean up
raise
# It was fine, give a normal answer
return result
class LoggingPool(Pool):
def apply_async(self, func, args=(), kwds={}, callback=None):
return Pool.apply_async(self, LogExceptions(func), args, kwds, callback)
def go():
print(1)
raise Exception()
print(2)
multiprocessing.log_to_stderr()
p = LoggingPool(processes=1)
p.apply_async(go)
p.close()
p.join()
Cela me donne:
1
[ERROR/PoolWorker-1] Traceback (most recent call last):
File "mpdebug.py", line 24, in __call__
result = self.__callable(*args, **kwargs)
File "mpdebug.py", line 44, in go
raise Exception()
Exception
Peut-être qu'il me manque quelque chose, mais n'est-ce pas ce que la méthode get
de l'objet Result renvoie? Voir Pools de processus .
classe multiprocessing.pool.AsyncResult
La classe du résultat renvoyé par Pool.apply_async () et Pool.map_async (). Get ([timeout])
Renvoie le résultat quand il arrive. Si timeout n'est pas None et que le résultat n'arrive pas dans timeout secondes, puis le multitraitement.TimeoutError est déclenché. Si la télécommande call a déclenché une exception alors cette exception sera sur-levée par get ().
Donc, en modifiant légèrement votre exemple, on peut faire
from multiprocessing import Pool
def go():
print(1)
raise Exception("foobar")
print(2)
p = Pool()
x = p.apply_async(go)
x.get()
p.close()
p.join()
Ce qui donne comme résultat
1
Traceback (most recent call last):
File "rob.py", line 10, in <module>
x.get()
File "/usr/lib/python2.6/multiprocessing/pool.py", line 422, in get
raise self._value
Exception: foobar
Ce n'est pas complètement satisfaisant, car cela n'imprime pas le suivi, mais c'est mieux que rien.
MISE À JOUR: Ce bogue a été corrigé dans Python 3.4, gracieuseté de Richard Oudkerk. Voir la méthode issue get de multiprocessing.pool.Async doit renvoyer une trace complète .
La solution avec le plus de votes au moment de la rédaction a un problème:
from multiprocessing import Pool
def go():
print(1)
raise Exception("foobar")
print(2)
p = Pool()
x = p.apply_async(go)
x.get() ## waiting here for go() to complete...
p.close()
p.join()
Comme @dfrankow l'a noté, il attendra x.get()
, ce qui gâcherait le point d'exécuter une tâche de manière asynchrone. Donc, pour une meilleure efficacité (en particulier si votre fonction de travail go
prend beaucoup de temps), je la changerais en:
from multiprocessing import Pool
def go(x):
print(1)
# task_that_takes_a_long_time()
raise Exception("Can't go anywhere.")
print(2)
return x**2
p = Pool()
results = []
for x in range(1000):
results.append( p.apply_async(go, [x]) )
p.close()
for r in results:
r.get()
Avantages : la fonction de travail est exécutée de manière asynchrone. Ainsi, par exemple, si vous exécutez plusieurs tâches sur plusieurs cœurs, ce sera beaucoup plus efficace que la solution d'origine.
Inconvénients : s'il existe une exception dans la fonction worker, celle-ci ne sera déclenchée qu'après le pool a terminé toutes les tâches. Cela peut ou peut ne pas être le comportement souhaitable. EDITE selon le commentaire de @ colinfang, qui a corrigé le problème.
J'ai eu du succès en enregistrant des exceptions avec ce décorateur:
import traceback, functools, multiprocessing
def trace_unhandled_exceptions(func):
@functools.wraps(func)
def wrapped_func(*args, **kwargs):
try:
func(*args, **kwargs)
except:
print 'Exception in '+func.__name__
traceback.print_exc()
return wrapped_func
avec le code dans la question, c'est
@trace_unhandled_exceptions
def go():
print(1)
raise Exception()
print(2)
p = multiprocessing.Pool(1)
p.apply_async(go)
p.close()
p.join()
Décorez simplement la fonction que vous transmettez à votre pool de processus. La clé de ce fonctionnement est @functools.wraps(func)
sinon le multitraitement renvoie une PicklingError
.
le code ci-dessus donne
1
Exception in go
Traceback (most recent call last):
File "<stdin>", line 5, in wrapped_func
File "<stdin>", line 4, in go
Exception
import logging
from multiprocessing import Pool
def proc_wrapper(func, *args, **kwargs):
"""Print exception because multiprocessing lib doesn't return them right."""
try:
return func(*args, **kwargs)
except Exception as e:
logging.exception(e)
raise
def go(x):
print x
raise Exception("foobar")
p = Pool()
p.apply_async(proc_wrapper, (go, 5))
p.join()
p.close()
J'ai créé un module RemoteException.py qui montre le suivi complet d'une exception dans un processus. Python2. Téléchargez-le et ajoutez-le à votre code:
import RemoteException
@RemoteException.showError
def go():
raise Exception('Error!')
if __== '__main__':
import multiprocessing
p = multiprocessing.Pool(processes = 1)
r = p.apply(go) # full traceback is shown here
Depuis que vous avez utilisé apply_sync
, je suppose que le cas d'utilisation consiste à effectuer certaines tâches de synchronisation. Utiliser le rappel pour la gestion est une autre option. Veuillez noter que cette option est disponible uniquement pour python 3.2 et supérieur et non disponible pour python2.7.
from multiprocessing import Pool
def callback(result):
print('success', result)
def callback_error(result):
print('error', result)
def go():
print(1)
raise Exception()
print(2)
p = Pool()
p.apply_async(go, callback=callback, error_callback=callback_error)
# You can do another things
p.close()
p.join()
J'essaierais d'utiliser pdb:
import pdb
import sys
def handler(type, value, tb):
pdb.pm()
sys.excepthook = handler
Puisqu'il existe déjà des réponses correctes pour multiprocessing.Pool
, je proposerai une solution en utilisant une approche différente pour la complétude.
Pour python >= 3.2
, la solution suivante semble être la plus simple:
from concurrent.futures import ProcessPoolExecutor, wait
def go():
print(1)
raise Exception()
print(2)
futures = []
with ProcessPoolExecutor() as p:
for i in range(10):
futures.append(p.submit(go))
results = [f.result() for f in futures]
Avantages:
Pour plus d'informations sur l'API, veuillez consulter: https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
De plus, si vous soumettez un grand nombre de tâches et souhaitez que votre processus principal échoue dès qu'une de vos tâches échoue, vous pouvez utiliser l'extrait de code suivant:
from concurrent.futures import ProcessPoolExecutor, wait, FIRST_EXCEPTION, as_completed
import time
def go():
print(1)
time.sleep(0.3)
raise Exception()
print(2)
futures = []
with ProcessPoolExecutor(1) as p:
for i in range(10):
futures.append(p.submit(go))
for f in as_completed(futures):
if f.exception() is not None:
for f in futures:
f.cancel()
break
[f.result() for f in futures]
Toutes les autres réponses n'échouent que lorsque toutes les tâches ont été exécutées.