Attraper l'exception d'un thread dans le thread d'appelant en Python


209

Je suis très nouveau en Python et en programmation multithread en général. Fondamentalement, j'ai un script qui copiera les fichiers vers un autre emplacement. Je voudrais que cela soit placé dans un autre thread afin que je puisse sortir ....pour indiquer que le script est toujours en cours d'exécution.

Le problème que j'ai, c'est que si les fichiers ne peuvent pas être copiés, il lèvera une exception. C'est correct si vous utilisez le thread principal; cependant, avoir le code suivant ne fonctionne pas:

try:
    threadClass = TheThread(param1, param2, etc.)
    threadClass.start()   ##### **Exception takes place here**
except:
    print "Caught an exception"

Dans la classe de thread elle-même, j'ai essayé de relancer l'exception, mais cela ne fonctionne pas. J'ai vu des gens ici poser des questions similaires, mais ils semblent tous faire quelque chose de plus spécifique que ce que j'essaie de faire (et je ne comprends pas très bien les solutions proposées). J'ai vu des gens mentionner l'utilisation de sys.exc_info(), mais je ne sais pas où ni comment l'utiliser.

Toute aide est grandement appréciée!

EDIT: Le code de la classe de thread est ci-dessous:

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

Pouvez-vous donner plus d'informations sur ce qui se passe à l'intérieur TheThread? Exemple de code peut-être?
jathanism

Sûr. Je modifierai ma réponse ci-dessus pour inclure quelques détails.
Phanto

1
Avez-vous envisagé de le changer pour que le thread principal soit le bit qui fait des trucs et que l'indicateur de progression se trouve dans le thread généré?
Dan Head

1
Dan Head, faites-vous référence au thread principal qui génère d'abord la fonction "..." puis exécute la fonction de copie? Cela pourrait fonctionner et éviter le problème des exceptions. Mais, j'aimerais toujours apprendre à enfiler correctement en python.
Phanto

Réponses:


115

Le problème est qu'il thread_obj.start()revient immédiatement. Le thread enfant que vous avez généré s'exécute dans son propre contexte, avec sa propre pile. Toute exception qui s'y produit se trouve dans le contexte du thread enfant et se trouve dans sa propre pile. Une façon dont je peux penser en ce moment pour communiquer ces informations au thread parent est en utilisant une sorte de message qui passe, donc vous pourriez examiner cela.

Essayez ceci pour la taille:

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()

5
Pourquoi ne pas rejoindre le fil au lieu de cette moche boucle while? Voir l' multiprocessingéquivalent: gist.github.com/2311116
schlamar

1
Pourquoi ne pas utiliser le modèle EventHook stackoverflow.com/questions/1092531/event-system-in-python/… basé sur la réponse @Lasse? Plutôt que la boucle?
Andre Miras

1
La file d'attente n'est pas le meilleur moyen de communiquer une erreur, sauf si vous souhaitez en avoir une file d'attente complète. Une construction bien meilleure est le filetage.Event ()
Muposat

1
Cela me semble dangereux. Que se passe-t-il lorsque le thread déclenche une exception juste après la bucket.get()relance Queue.Empty? Ensuite, le fil join(0.1)se terminera et isAlive() is False, et vous manquerez votre exception.
Steve

1
Queueest inutile dans ce cas simple - vous pouvez simplement stocker les informations d'exception en tant que propriété de ExcThreadtant que vous vous assurez que cela se run()termine juste après l'exception (ce qu'il fait dans cet exemple simple). Ensuite, vous relancez simplement l'exception après (ou pendant) t.join(). Il n'y a aucun problème de synchronisation car il join()s'assure que le thread est terminé. Voir la réponse de Rok Strniša ci-dessous stackoverflow.com/a/12223550/126362
ejm

42

Le concurrent.futuresmodule simplifie le travail dans des threads (ou processus) séparés et gère toutes les exceptions résultantes:

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.futuresest inclus avec Python 3.2 et est disponible en tant que module rétroportéfutures pour les versions antérieures.


5
Bien que cela ne fasse pas exactement ce que l'OP a demandé, c'est exactement l'indice dont j'avais besoin. Je vous remercie.
Mad Physicist

2
Et avec concurrent.futures.as_completed, vous pouvez être immédiatement averti lorsque des exceptions sont levées: stackoverflow.com/questions/2829329/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

1
Ce code bloque le thread principal. Comment procédez-vous de manière asynchrone?
Nikolay Shindarov

40

Il y a beaucoup de réponses vraiment étrangement compliquées à cette question. Suis-je trop simplifier cela, car cela me semble suffisant pour la plupart des choses.

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):
        super(PropagatingThread, self).join()
        if self.exc:
            raise self.exc
        return self.ret

Si vous êtes certain que vous n'exécuterez jamais que sur l'une ou l'autre version de Python, vous pouvez réduire la run()méthode à la seule version modifiée (si vous n'exécutez que sur des versions de Python avant 3), ou juste la version propre (si vous n'exécutez que sur des versions de Python commençant par 3).

Exemple d'utilisation:

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()

Et vous verrez l'exception levée sur l'autre thread lorsque vous vous joindrez.

Si vous utilisez sixou sur Python 3 uniquement, vous pouvez améliorer les informations de trace de pile que vous obtenez lorsque l'exception est levée à nouveau. Au lieu de seulement la pile au point de la jointure, vous pouvez encapsuler l'exception interne dans une nouvelle exception externe et obtenir les deux traces de pile avec

six.raise_from(RuntimeError('Exception in thread'),self.exc)

ou

raise RuntimeError('Exception in thread') from self.exc

1
Je ne sais pas pourquoi cette réponse n'est pas plus populaire non plus. Il y en a d'autres ici qui font aussi de la propagation simple, mais nécessitent l'extension d'une classe et la substitution. Celui-ci fait juste ce que beaucoup attendraient et ne nécessite que de passer de Thread à ProagatingThread. Et 4 onglets d'espace, donc mon copier / coller était trivial :-) ... la seule amélioration que je suggérerais est d'utiliser six.raise_from () afin que vous obteniez un bel ensemble imbriqué de traces de pile, au lieu de simplement la pile pour le site de la relance.
aggieNick02

Merci beaucoup. Solution très simple.
sonulohani

Mon problème est que j'ai plusieurs fils enfants. Les jointures sont exécutées en séquence et l'exception peut être levée à partir des threads joints plus tard. Existe-t-il une solution simple à mon problème? exécuter la jointure simultanément?
chuan

Merci, cela fonctionne parfaitement! Je ne sais pas pourquoi il n'est pas géré directement par python mais…
GG.

C'est définitivement la réponse la plus utile, cette sulution est beaucoup plus générale que d'autres encore simple. Va l'utiliser dans un projet!
Konstantin Sekeresh

30

Bien qu'il ne soit pas possible d'attraper directement une exception levée dans un thread différent, voici un code pour obtenir de manière très transparente quelque chose de très proche de cette fonctionnalité. Votre thread enfant doit sous- ExThreadclasser la classe au lieu de threading.Threadet le thread parent doit appeler la child_thread.join_with_exception()méthode au lieu de child_thread.join()lorsqu'il attend que le thread termine son travail.

Détails techniques de cette implémentation: lorsque le thread enfant lève une exception, elle est transmise au parent via a Queueet renvoyée dans le thread parent. Notez qu'il n'y a pas d'attente occupée dans cette approche.

#!/usr/bin/env python

import sys
import threading
import Queue

class ExThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.__status_queue = Queue.Queue()

    def run_with_exception(self):
        """This method should be overriden."""
        raise NotImplementedError

    def run(self):
        """This method should NOT be overriden."""
        try:
            self.run_with_exception()
        except BaseException:
            self.__status_queue.put(sys.exc_info())
        self.__status_queue.put(None)

    def wait_for_exc_info(self):
        return self.__status_queue.get()

    def join_with_exception(self):
        ex_info = self.wait_for_exc_info()
        if ex_info is None:
            return
        else:
            raise ex_info[1]

class MyException(Exception):
    pass

class MyThread(ExThread):
    def __init__(self):
        ExThread.__init__(self)

    def run_with_exception(self):
        thread_name = threading.current_thread().name
        raise MyException("An error in thread '{}'.".format(thread_name))

def main():
    t = MyThread()
    t.start()
    try:
        t.join_with_exception()
    except MyException as ex:
        thread_name = threading.current_thread().name
        print "Caught a MyException in thread '{}': {}".format(thread_name, ex)

if __name__ == '__main__':
    main()

1
Ne voudriez-vous pas attraper BaseException, non Exception? Tout ce que vous faites, c'est propager l'exception de l'un Threadvers l'autre. À l'heure actuelle, IE, a KeyboardInterrupt, serait silencieusement ignoré s'il était levé dans un thread d'arrière-plan.
ArtOfWarfare du

join_with_exceptionse bloque indéfiniment s'il est appelé une deuxième fois sur un thread mort. Correctif: github.com/fraserharris/threading-extensions/blob/master/…
Fraser Harris

Je ne pense pas que ce Queuesoit nécessaire; voir mon commentaire sur la réponse de @ Santa. Vous pouvez le simplifier jusqu'à quelque chose comme la réponse de Rok Strniša ci-dessous stackoverflow.com/a/12223550/126362
ejm

22

Si une exception se produit dans un thread, la meilleure façon est de la relancer dans le thread appelant pendant join. Vous pouvez obtenir des informations sur l'exception en cours de traitement à l'aide de la sys.exc_info()fonction. Ces informations peuvent simplement être stockées en tant que propriété de l'objet thread jusqu'à joince qu'elles soient appelées, moment auquel elles peuvent être remontées.

Notez qu'un Queue.Queue(comme suggéré dans d'autres réponses) n'est pas nécessaire dans ce cas simple où le thread lève au plus 1 exception et se termine juste après avoir levé une exception . Nous évitons les conditions de concurrence en attendant simplement la fin du thread.

Par exemple, étendez ExcThread(ci-dessous), en remplaçant excRun(au lieu de run).

Python 2.x:

import threading

class ExcThread(threading.Thread):
  def excRun(self):
    pass

  def run(self):
    self.exc = None
    try:
      # Possibly throws an exception
      self.excRun()
    except:
      import sys
      self.exc = sys.exc_info()
      # Save details of the exception thrown but don't rethrow,
      # just complete the function

  def join(self):
    threading.Thread.join(self)
    if self.exc:
      msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
      new_exc = Exception(msg)
      raise new_exc.__class__, new_exc, self.exc[2]

Python 3.x:

La forme à 3 arguments pour raiseest partie en Python 3, changez donc la dernière ligne en:

raise new_exc.with_traceback(self.exc[2])

2
Pourquoi utilisez-vous threading.Thread.join (self) au lieu de super (ExcThread, self) .join ()?
Richard Möhn

9

concurrent.futures.as_completed

https://docs.python.org/3.7/library/concurrent.futures.html#concurrent.futures.as_completed

La solution suivante:

  • retourne au thread principal immédiatement lorsqu'une exception est appelée
  • ne nécessite aucune classe supplémentaire définie par l'utilisateur car elle n'a pas besoin:
    • un explicite Queue
    • pour ajouter un sauf autre autour de votre fil de travail

La source:

#!/usr/bin/env python3

import concurrent.futures
import time

def func_that_raises(do_raise):
    for i in range(3):
        print(i)
        time.sleep(0.1)
    if do_raise:
        raise Exception()
    for i in range(3):
        print(i)
        time.sleep(0.1)

with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
    futures = []
    futures.append(executor.submit(func_that_raises, False))
    futures.append(executor.submit(func_that_raises, True))
    for future in concurrent.futures.as_completed(futures):
        print(repr(future.exception()))

Sortie possible:

0
0
1
1
2
2
0
Exception()
1
2
None

Il n'est malheureusement pas possible de tuer des futures pour annuler les autres car on échoue:

Si vous faites quelque chose comme:

for future in concurrent.futures.as_completed(futures):
    if future.exception() is not None:
        raise future.exception()

puis l' withattrape et attend la fin du deuxième thread avant de continuer. Le comportement suivant est similaire:

for future in concurrent.futures.as_completed(futures):
    future.result()

depuis future.result()relance l'exception si elle se produit.

Si vous souhaitez quitter l'intégralité du processus Python, vous pouvez vous en sortir os._exit(0), mais cela signifie probablement que vous avez besoin d'un refactor.

Classe personnalisée avec une sémantique d'exception parfaite

J'ai fini par coder pour moi-même l'interface parfaite: la bonne façon de limiter le nombre maximal de threads s'exécutant simultanément? section "Exemple de file d'attente avec gestion des erreurs". Cette classe vise à être à la fois pratique et vous donne un contrôle total sur la soumission et la gestion des résultats / erreurs.

Testé sur Python 3.6.7, Ubuntu 18.04.


4

C'était un petit problème désagréable, et j'aimerais jeter ma solution. Certaines autres solutions que j'ai trouvées (async.io par exemple) semblaient prometteuses mais présentaient aussi un peu une boîte noire. L'approche de la file d'attente / boucle d'événements vous relie en quelque sorte à une certaine implémentation. Le code source de contrats à terme simultanés, cependant, est d'environ 1000 lignes seulement, et facile à comprendre . Cela m'a permis de résoudre facilement mon problème: créer des threads de travail ad hoc sans trop de configuration et pouvoir intercepter des exceptions dans le thread principal.

Ma solution utilise l'API à terme simultanée et l'API de threading. Il vous permet de créer un travailleur qui vous donne à la fois le fil et l'avenir. De cette façon, vous pouvez rejoindre le thread pour attendre le résultat:

worker = Worker(test)
thread = worker.start()
thread.join()
print(worker.future.result())

... ou vous pouvez laisser le travailleur simplement envoyer un rappel une fois terminé:

worker = Worker(test)
thread = worker.start(lambda x: print('callback', x))

... ou vous pouvez boucler jusqu'à la fin de l'événement:

worker = Worker(test)
thread = worker.start()

while True:
    print("waiting")
    if worker.future.done():
        exc = worker.future.exception()
        print('exception?', exc)
        result = worker.future.result()
        print('result', result)           
        break
    time.sleep(0.25)

Voici le code:

from concurrent.futures import Future
import threading
import time

class Worker(object):
    def __init__(self, fn, args=()):
        self.future = Future()
        self._fn = fn
        self._args = args

    def start(self, cb=None):
        self._cb = cb
        self.future.set_running_or_notify_cancel()
        thread = threading.Thread(target=self.run, args=())
        thread.daemon = True #this will continue thread execution after the main thread runs out of code - you can still ctrl + c or kill the process
        thread.start()
        return thread

    def run(self):
        try:
            self.future.set_result(self._fn(*self._args))
        except BaseException as e:
            self.future.set_exception(e)

        if(self._cb):
            self._cb(self.future.result())

... et la fonction de test:

def test(*args):
    print('args are', args)
    time.sleep(2)
    raise Exception('foo')

2

En tant que noobie de Threading, il m'a fallu beaucoup de temps pour comprendre comment implémenter le code de Mateusz Kobos (ci-dessus). Voici une version clarifiée pour vous aider à comprendre comment l'utiliser.

#!/usr/bin/env python

import sys
import threading
import Queue

class ExThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.__status_queue = Queue.Queue()

    def run_with_exception(self):
        """This method should be overriden."""
        raise NotImplementedError

    def run(self):
        """This method should NOT be overriden."""
        try:
            self.run_with_exception()
        except Exception:
            self.__status_queue.put(sys.exc_info())
        self.__status_queue.put(None)

    def wait_for_exc_info(self):
        return self.__status_queue.get()

    def join_with_exception(self):
        ex_info = self.wait_for_exc_info()
        if ex_info is None:
            return
        else:
            raise ex_info[1]

class MyException(Exception):
    pass

class MyThread(ExThread):
    def __init__(self):
        ExThread.__init__(self)

    # This overrides the "run_with_exception" from class "ExThread"
    # Note, this is where the actual thread to be run lives. The thread
    # to be run could also call a method or be passed in as an object
    def run_with_exception(self):
        # Code will function until the int
        print "sleeping 5 seconds"
        import time
        for i in 1, 2, 3, 4, 5:
            print i
            time.sleep(1) 
        # Thread should break here
        int("str")
# I'm honestly not sure why these appear here? So, I removed them. 
# Perhaps Mateusz can clarify?        
#         thread_name = threading.current_thread().name
#         raise MyException("An error in thread '{}'.".format(thread_name))

if __name__ == '__main__':
    # The code lives in MyThread in this example. So creating the MyThread 
    # object set the code to be run (but does not start it yet)
    t = MyThread()
    # This actually starts the thread
    t.start()
    print
    print ("Notice 't.start()' is considered to have completed, although" 
           " the countdown continues in its new thread. So you code "
           "can tinue into new processing.")
    # Now that the thread is running, the join allows for monitoring of it
    try:
        t.join_with_exception()
    # should be able to be replace "Exception" with specific error (untested)
    except Exception, e: 
        print
        print "Exceptioon was caught and control passed back to the main thread"
        print "Do some handling here...or raise a custom exception "
        thread_name = threading.current_thread().name
        e = ("Caught a MyException in thread: '" + 
             str(thread_name) + 
             "' [" + str(e) + "]")
        raise Exception(e) # Or custom class of exception, such as MyException

2

De manière similaire à celle de RickardSjogren sans Queue, sys etc. mais aussi sans certains écouteurs de signaux: exécutez directement un gestionnaire d'exceptions qui correspond à un bloc except.

#!/usr/bin/env python3

import threading

class ExceptionThread(threading.Thread):

    def __init__(self, callback=None, *args, **kwargs):
        """
        Redirect exceptions of thread to an exception handler.

        :param callback: function to handle occured exception
        :type callback: function(thread, exception)
        :param args: arguments for threading.Thread()
        :type args: tuple
        :param kwargs: keyword arguments for threading.Thread()
        :type kwargs: dict
        """
        self._callback = callback
        super().__init__(*args, **kwargs)

    def run(self):
        try:
            if self._target:
                self._target(*self._args, **self._kwargs)
        except BaseException as e:
            if self._callback is None:
                raise e
            else:
                self._callback(self, e)
        finally:
            # Avoid a refcycle if the thread is running a function with
            # an argument that has a member that points to the thread.
            del self._target, self._args, self._kwargs, self._callback

Seuls self._callback et le bloc except dans run () s'ajoutent au threading normal.


2

Je sais que je suis un peu en retard pour la fête ici, mais je rencontrais un problème très similaire, mais cela incluait l'utilisation de tkinter comme interface graphique, et la boucle principale a rendu impossible l'utilisation des solutions qui dépendent de .join (). J'ai donc adapté la solution donnée dans l'EDIT de la question d'origine, mais je l'ai rendue plus générale pour la rendre plus facile à comprendre pour les autres.

Voici la nouvelle classe de threads en action:

import threading
import traceback
import logging


class ExceptionThread(threading.Thread):
    def __init__(self, *args, **kwargs):
        threading.Thread.__init__(self, *args, **kwargs)

    def run(self):
        try:
            if self._target:
                self._target(*self._args, **self._kwargs)
        except Exception:
            logging.error(traceback.format_exc())


def test_function_1(input):
    raise IndexError(input)


if __name__ == "__main__":
    input = 'useful'

    t1 = ExceptionThread(target=test_function_1, args=[input])
    t1.start()

Bien sûr, vous pouvez toujours lui faire gérer l'exception d'une autre manière de la journalisation, comme l'impression ou la sortie sur la console.

Cela vous permet d'utiliser la classe ExceptionThread exactement comme vous le feriez avec la classe Thread, sans aucune modification spéciale.


1

Une méthode que j'aime est basée sur le modèle d'observateur . Je définis une classe de signal que mon thread utilise pour émettre des exceptions aux écouteurs. Il peut également être utilisé pour renvoyer des valeurs à partir de threads. Exemple:

import threading

class Signal:
    def __init__(self):
        self._subscribers = list()

    def emit(self, *args, **kwargs):
        for func in self._subscribers:
            func(*args, **kwargs)

    def connect(self, func):
        self._subscribers.append(func)

    def disconnect(self, func):
        try:
            self._subscribers.remove(func)
        except ValueError:
            raise ValueError('Function {0} not removed from {1}'.format(func, self))


class WorkerThread(threading.Thread):

    def __init__(self, *args, **kwargs):
        super(WorkerThread, self).__init__(*args, **kwargs)
        self.Exception = Signal()
        self.Result = Signal()

    def run(self):
        if self._Thread__target is not None:
            try:
                self._return_value = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
            except Exception as e:
                self.Exception.emit(e)
            else:
                self.Result.emit(self._return_value)

if __name__ == '__main__':
    import time

    def handle_exception(exc):
        print exc.message

    def handle_result(res):
        print res

    def a():
        time.sleep(1)
        raise IOError('a failed')

    def b():
        time.sleep(2)
        return 'b returns'

    t = WorkerThread(target=a)
    t2 = WorkerThread(target=b)
    t.Exception.connect(handle_exception)
    t2.Result.connect(handle_result)
    t.start()
    t2.start()

    print 'Threads started'

    t.join()
    t2.join()
    print 'Done'

Je n'ai pas assez d'expérience de travail avec les threads pour prétendre qu'il s'agit d'une méthode totalement sûre. Mais cela a fonctionné pour moi et j'aime la flexibilité.


vous déconnectez-vous après join ()?
ealeon

Non, mais je suppose que ce serait une bonne idée, donc vous n'avez pas de références à des trucs inutilisés.
RickardSjogren

j'ai remarqué que "handle_exception" fait toujours partie d'un thread enfant. besoin de moyen de le passer à l'appelant de thread
ealeon

1

L'utilisation d'exceptions nues n'est pas une bonne pratique car vous attrapez généralement plus que ce que vous attendez.

Je suggérerais de modifier le exceptpour intercepter UNIQUEMENT l'exception que vous souhaitez gérer. Je ne pense pas que l'élévation ait l'effet souhaité, car lorsque vous allez instancier TheThreadà l'extérieur try, si elle déclenche une exception, l'affectation ne se produira jamais.

Au lieu de cela, vous voudrez peut-être simplement l'alerter et continuer, comme:

def run(self):
    try:
       shul.copytree(self.sourceFolder, self.destFolder)
    except OSError, err:
       print err

Ensuite, lorsque cette exception est interceptée, vous pouvez la gérer à cet endroit. Ensuite, lorsque l'extérieur trydétecte une exception TheThread, vous savez que ce ne sera pas celui que vous avez déjà géré et vous aidera à isoler votre flux de processus.


1
Eh bien, s'il y a une erreur dans ce thread, je veux que le programme complet informe l'utilisateur qu'il y a eu un problème et se termine avec élégance. Pour cette raison, je veux que le thread principal intercepte et gère toutes les exceptions. Cependant, le problème existe toujours où si TheThread lève une exception, try / except du thread principal ne l'attrapera toujours pas. Je pourrais demander au thread de détecter l'exception et de retourner un faux indiquant que l'opération a échoué. Cela permettrait d'obtenir le même résultat souhaité, mais j'aimerais toujours savoir comment intercepter correctement une exception de sous-thread.
Phanto

1

Un moyen simple d'attraper l'exception de thread et de communiquer à nouveau avec la méthode de l'appelant pourrait être en passant le dictionnaire ou une liste à la workerméthode.

Exemple (passage du dictionnaire à la méthode de travail):

import threading

def my_method(throw_me):
    raise Exception(throw_me)

def worker(shared_obj, *args, **kwargs):
    try:
        shared_obj['target'](*args, **kwargs)
    except Exception as err:
        shared_obj['err'] = err

shared_obj = {'err':'', 'target': my_method}
throw_me = "Test"

th = threading.Thread(target=worker, args=(shared_obj, throw_me), kwargs={})
th.start()
th.join()

if shared_obj['err']:
    print(">>%s" % shared_obj['err'])

1

Envelopper le fil avec un stockage d'exception.

import threading
import sys
class ExcThread(threading.Thread):

    def __init__(self, target, args = None):
        self.args = args if args else []
        self.target = target
        self.exc = None
        threading.Thread.__init__(self)

    def run(self):
        try:
            self.target(*self.args)
            raise Exception('An error occured here.')
        except Exception:
            self.exc=sys.exc_info()

def main():
    def hello(name):
        print(!"Hello, {name}!")
    thread_obj = ExcThread(target=hello, args=("Jack"))
    thread_obj.start()

    thread_obj.join()
    exc = thread_obj.exc
    if exc:
        exc_type, exc_obj, exc_trace = exc
        print(exc_type, ':',exc_obj, ":", exc_trace)

main()

0

pygolang fournit sync.WorkGroup qui, en particulier, propage l'exception des threads de travail générés au thread principal. Par exemple:

#!/usr/bin/env python
"""This program demostrates how with sync.WorkGroup an exception raised in
spawned thread is propagated into main thread which spawned the worker."""

from __future__ import print_function
from golang import sync, context

def T1(ctx, *argv):
    print('T1: run ... %r' % (argv,))
    raise RuntimeError('T1: problem')

def T2(ctx):
    print('T2: ran ok')

def main():
    wg = sync.WorkGroup(context.background())
    wg.go(T1, [1,2,3])
    wg.go(T2)

    try:
        wg.wait()
    except Exception as e:
        print('Tmain: caught exception: %r\n' %e)
        # reraising to see full traceback
        raise

if __name__ == '__main__':
    main()

donne ce qui suit lors de l'exécution:

T1: run ... ([1, 2, 3],)
T2: ran ok
Tmain: caught exception: RuntimeError('T1: problem',)

Traceback (most recent call last):
  File "./x.py", line 28, in <module>
    main()
  File "./x.py", line 21, in main
    wg.wait()
  File "golang/_sync.pyx", line 198, in golang._sync.PyWorkGroup.wait
    pyerr_reraise(pyerr)
  File "golang/_sync.pyx", line 178, in golang._sync.PyWorkGroup.go.pyrunf
    f(pywg._pyctx, *argv, **kw)
  File "./x.py", line 10, in T1
    raise RuntimeError('T1: problem')
RuntimeError: T1: problem

Le code d'origine de la question serait simplement:

    wg = sync.WorkGroup(context.background())

    def _(ctx):
        shul.copytree(sourceFolder, destFolder)
    wg.go(_)

    # waits for spawned worker to complete and, on error, reraises
    # its exception on the main thread.
    wg.wait()
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.