Cattura l'eccezione di un thread nel thread del chiamante in Python


209

Sono molto nuovo di Python e della programmazione multithread in generale. Fondamentalmente, ho uno script che copia i file in un'altra posizione. Vorrei che questo fosse inserito in un altro thread in modo da poterlo produrre.... per indicare che lo script è ancora in esecuzione.

Il problema che sto riscontrando è che se i file non possono essere copiati genererà un'eccezione. Questo va bene se in esecuzione nel thread principale; tuttavia, il seguente codice non funziona:

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

Nella stessa classe thread, ho provato a rilanciare l'eccezione, ma non funziona. Ho visto persone qui porre domande simili, ma sembrano tutti fare qualcosa di più specifico di quello che sto cercando di fare (e non capisco bene le soluzioni offerte). Ho visto persone menzionare l'uso disys.exc_info() , tuttavia non so dove o come usarlo.

Tutto l'aiuto è molto apprezzato!

EDIT: il codice per la classe thread è di seguito:

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

Puoi fornire maggiori informazioni su ciò che sta accadendo all'interno di TheThread? Esempio di codice forse?
jatanismo,

Sicuro. Modificherò la mia risposta sopra per includere alcuni dettagli.
Phanto,

1
Hai mai pensato di cambiarlo in modo che il thread principale sia il bit che fa cose e l'indicatore di progresso sia nel thread generato?
Dan Head,

1
Dan Head, ti riferisci al thread principale prima di generare la funzione "..." e quindi eseguire la funzione di copia? Ciò potrebbe funzionare ed evitare il problema delle eccezioni. Ma vorrei ancora imparare come inserire correttamente Python.
Phanto,

Risposte:


115

Il problema è che thread_obj.start() ritorna immediatamente. Il thread figlio che hai generato viene eseguito nel proprio contesto, con il proprio stack. Qualsiasi eccezione che si verifica è nel contesto del thread figlio ed è nel proprio stack. Un modo in cui riesco a pensare in questo momento per comunicare queste informazioni al thread principale è utilizzando una sorta di passaggio di messaggi, quindi potresti esaminarlo.

Prova questo per dimensioni:

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
Perché non unire il thread invece di questo brutto ciclo while? Vedi l' multiprocessingequivalente: gist.github.com/2311116
schlamar l'

1
Perché non utilizzare il modello EventHook stackoverflow.com/questions/1092531/event-system-in-python/... sulla base di @Lasse risposta? Piuttosto che la cosa del ciclo?
Andre Miras,

1
La coda non è il mezzo migliore per comunicare un errore, a meno che non si desideri avere una coda completa di essi. Un costrutto molto migliore è threading.Event ()
Muposat,

1
Questo non mi sembra sicuro. Cosa succede quando il thread solleva un'eccezione subito dopo i bucket.get()rilanci Queue.Empty? Quindi il thread join(0.1)verrà completato e isAlive() is Falsemancherai la tua eccezione.
Steve,

1
Queuenon è necessario in questo semplice caso: puoi semplicemente archiviare le informazioni sull'eccezione come una proprietà ExcThreadpurché ti assicuri che run()venga completato subito dopo l'eccezione (cosa che fa in questo semplice esempio). Quindi devi semplicemente sollevare nuovamente l'eccezione dopo (o durante) t.join(). Non ci sono problemi di sincronizzazione perché si join()assicura che il thread sia stato completato. Si veda la risposta di Rok Strniša sotto stackoverflow.com/a/12223550/126362
ejm

42

Il concurrent.futuresmodulo semplifica il lavoro in thread (o processi) separati e gestisce eventuali eccezioni risultanti:

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è incluso in Python 3.2 ed è disponibile come modulo di backportfutures per le versioni precedenti.


5
Sebbene ciò non faccia esattamente ciò che l'OP ha chiesto, è esattamente il suggerimento di cui avevo bisogno. Grazie.
Mad Physicist,

2
E con concurrent.futures.as_completed, è possibile ottenere immediatamente notificato eccezioni vengono sollevate: stackoverflow.com/questions/2829329/...
Ciro Santilli郝海东冠状病六四事件法轮功

1
Questo codice sta bloccando il thread principale. Come si fa in modo asincrono?
Nikolay Shindarov,

40

Ci sono molte risposte stranamente complicate a questa domanda. Sto semplificando troppo questo, perché questo mi sembra sufficiente per la maggior parte delle cose.

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

Se sei sicuro che eseguirai solo su una o l'altra versione di Python, potresti ridurre il run()metodo solo alla versione alterata (se eseguirai solo versioni di Python precedenti alla 3), oppure solo la versione pulita (se eseguirai solo versioni di Python che iniziano con 3).

Esempio di utilizzo:

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

E vedrai l'eccezione sollevata sull'altro thread quando ti iscrivi.

Se si sta utilizzando sixo solo su Python 3, è possibile migliorare le informazioni di traccia dello stack ottenute quando viene sollevata nuovamente l'eccezione. Invece di solo lo stack nel punto del join, è possibile racchiudere l'eccezione interna in una nuova eccezione esterna e ottenere entrambe le tracce dello stack con

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

o

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

1
Non sono sicuro del perché questa risposta non sia neanche più popolare. Ci sono altri qui che fanno anche semplice propagazione, ma richiedono l'estensione di una classe e l'override. Questo fa proprio quello che molti si aspetterebbero e richiede solo il passaggio da Thread a ProagatingThread. E 4 schede spaziali, quindi il mio copia / incolla è stato banale :-) ... l'unico miglioramento che suggerirei è l'uso di six.raise_from () in modo da ottenere un bel set nidificato di tracce dello stack, anziché solo lo stack per il sito del rilancio.
aggieNick02

Grazie mille. Soluzione molto semplice.
Sonulohani,

Il mio problema è che ho più thread figlio. I join vengono eseguiti in sequenza e l'eccezione potrebbe essere sollevata dai thread aggiunti successivamente. C'è una soluzione semplice al mio problema? eseguire il join contemporaneamente?
chuan,

Grazie, funziona perfettamente! Non so perché non sia gestito direttamente da Python Tho ...
GG.

Questa è sicuramente la risposta più utile, questa soluzione è molto più generale di altre ma semplice. Lo useremo in un progetto!
Konstantin Sekeresh,

30

Sebbene non sia possibile rilevare direttamente un'eccezione generata in un thread diverso, ecco un codice per ottenere in modo abbastanza trasparente qualcosa di molto simile a questa funzionalità. Il thread figlio deve sottoclassare la ExThreadclasse anziché threading.Threade il thread padre deve chiamare il child_thread.join_with_exception()metodo anziché child_thread.join()durante l'attesa del completamento del processo.

Dettagli tecnici di questa implementazione: quando il thread figlio genera un'eccezione, viene passato al padre attraverso a Queuee gettato di nuovo nel thread padre. Si noti che in questo approccio non è necessario attendere.

#!/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
Non vorresti catturare BaseException, no Exception? Tutto quello che stai facendo è propagare l'eccezione da una Threada un'altra. In questo momento, IE, a KeyboardInterrupt, verrebbe ignorato in silenzio se fosse generato in un thread in background.
ArtOfWarfare il

join_with_exceptionpende indefinitamente se chiamato una seconda volta su un thread morto. Correzione: github.com/fraserharris/threading-extensions/blob/master/…
Fraser Harris,

Non penso Queuesia necessario; vedi il mio commento alla risposta di @ Santa. È possibile semplificare giù a qualcosa come la risposta di Rok Strniša sotto stackoverflow.com/a/12223550/126362
ejm

22

Se si verifica un'eccezione in un thread, il modo migliore è di ri-sollevarlo nel thread del chiamante durante join. È possibile ottenere informazioni sull'eccezione attualmente gestita utilizzando la sys.exc_info()funzione. Queste informazioni possono essere semplicemente archiviate come proprietà dell'oggetto thread fino a quando non joinvengono chiamate, a quel punto possono essere ri-sollevate.

Si noti che a Queue.Queue (come suggerito in altre risposte) non è necessario in questo semplice caso in cui il thread genera al massimo 1 eccezione e si completa subito dopo aver lanciato un'eccezione . Evitiamo le condizioni di gara semplicemente aspettando il completamento del thread.

Ad esempio, estendi ExcThread (sotto), sovrascrivendo excRun(anziché 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:

Il modulo dell'argomento 3 per raiseè andato in Python 3, quindi cambia l'ultima riga in:

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

2
Perché usi threading.Thread.join (self) invece di 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 seguente soluzione:

  • ritorna immediatamente al thread principale quando viene chiamata un'eccezione
  • non richiede classi extra definite dall'utente perché non ha bisogno di:
    • un esplicito Queue
    • per aggiungere un altro tranne il tuo thread di lavoro

Fonte:

#!/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()))

Uscita possibile:

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

Sfortunatamente non è possibile uccidere i futuri per cancellare gli altri quando uno fallisce:

Se fai qualcosa del tipo:

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

quindi lo withprende e attende che il secondo thread termini prima di continuare. Quanto segue si comporta in modo simile:

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

poiché future.result()ripristina l'eccezione se si è verificato.

Se vuoi abbandonare l'intero processo Python, potresti cavartela os._exit(0), ma questo probabilmente significa che hai bisogno di un refactor.

Classe personalizzata con semantica di eccezione perfetta

Ho finito per scrivere l'interfaccia perfetta per me su: Il modo giusto per limitare il numero massimo di thread in esecuzione contemporaneamente? sezione "Esempio di coda con gestione degli errori". Quella classe mira ad essere sia conveniente, che ti dà il controllo totale sull'invio e sulla gestione di risultati / errori.

Testato su Python 3.6.7, Ubuntu 18.04.


4

Questo è stato un piccolo problema spiacevole e mi piacerebbe gettare la mia soluzione. Alcune altre soluzioni che ho trovato (ad esempio async.io) sembravano promettenti ma presentavano anche una scatola nera. L'approccio di coda / ciclo di eventi ti lega a una certa implementazione. Il codice sorgente dei futuri concorrenti, tuttavia, è di circa 1000 righe e di facile comprensione . Mi ha permesso di risolvere facilmente il mio problema: creare thread di lavoro ad-hoc senza molta configurazione e riuscire a rilevare le eccezioni nel thread principale.

La mia soluzione utilizza l'API futures e l'API threading simultanei. Ti permette di creare un lavoratore che ti dia sia il filo che il futuro. In questo modo, puoi unirti al thread per attendere il risultato:

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

... oppure puoi consentire al lavoratore di inviare una richiamata al termine:

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

... oppure puoi eseguire il ciclo fino al completamento dell'evento:

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)

Ecco il codice:

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

... e la funzione di test:

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

2

Come noobie di Threading, mi ci è voluto molto tempo per capire come implementare il codice di Mateusz Kobos (sopra). Ecco una versione chiarita per aiutare a capire come usarlo.

#!/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

In modo simile a quello di RickardSjogren senza Queue, sys ecc. Ma anche senza alcuni ascoltatori di segnali: esegui direttamente un gestore di eccezioni che corrisponde a un blocco di eccezione.

#!/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

Solo self._callback e il blocco di eccezione in run () è aggiuntivo al normale threading.


2

So di essere un po 'in ritardo alla festa qui ma avevo un problema molto simile ma includeva l'uso di tkinter come una GUI e il mainloop rendeva impossibile usare una delle soluzioni che dipendono da .join (). Pertanto ho adattato la soluzione fornita nell'EDIT della domanda originale, ma ho reso più generale per facilitare la comprensione per gli altri.

Ecco la nuova classe di thread in azione:

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

Ovviamente puoi sempre far sì che l'eccezione gestisca l'eccezione in altro modo dalla registrazione, come stamparla o averla in output sulla console.

Ciò ti consente di utilizzare la classe ExceptionThread esattamente come faresti con la classe Thread, senza alcuna modifica speciale.


1

Un metodo a cui sono affezionato si basa sul modello di osservatore . Definisco una classe di segnale che il mio thread usa per emettere eccezioni agli ascoltatori. Può anche essere usato per restituire valori dai thread. Esempio:

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'

Non ho abbastanza esperienza di lavoro con i thread per affermare che questo è un metodo completamente sicuro. Ma ha funzionato per me e mi piace la flessibilità.


ti disconnetti dopo join ()?
ealeon,

Non lo so, ma immagino che sarebbe una buona idea, quindi non hai riferimenti a cose inutilizzate in giro.
RickardSjogren,

ho notato che "handle_exception" fa ancora parte di un thread figlio. ho bisogno di un modo per passarlo al thread chiamante
ealeon

1

L'uso di eccezioni nude non è una buona pratica perché di solito prendi più di quanto preveda.

Suggerirei di modificare il exceptper catturare SOLO l'eccezione che vorresti gestire. Non penso che alzarlo abbia l'effetto desiderato, perché quando vai a creare un'istanza TheThreadnell'esternotry , se solleva un'eccezione, l'incarico non avverrà mai.

Invece potresti voler solo avvisare e andare avanti, come ad esempio:

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

Quindi, quando viene rilevata tale eccezione, è possibile gestirla lì. Quindi, quando l'esterno tryrileva un'eccezione TheThread, sai che non sarà quello che hai già gestito e ti aiuterà a isolare il flusso del processo.


1
Bene, se c'è un errore in quel thread, voglio che il programma completo informi l'utente che c'è stato un problema e che si chiude con grazia. Per questo motivo, voglio che il thread principale catturi e gestisca tutte le eccezioni. Tuttavia, il problema esiste ancora se, se TheThread genera un'eccezione, il thread principale try / tranne non lo rileva ancora. Potrei fare in modo che il thread rilevi l'eccezione e restituisca un falso indicando che l'operazione non è andata a buon fine. Ciò consentirebbe di ottenere lo stesso risultato desiderato, ma vorrei comunque sapere come rilevare correttamente un'eccezione del sottoprocesso.
Phanto,

1

Un modo semplice per rilevare l'eccezione del thread e comunicare nuovamente al metodo del chiamante potrebbe essere passare il dizionario o un elenco al workermetodo.

Esempio (passaggio del dizionario al metodo worker):

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

Avvolgere il thread con l'archiviazione delle eccezioni.

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 fornisce sync.WorkGroup che, in particolare, propaga l'eccezione dai thread di lavoro generati al thread principale. Per esempio:

#!/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()

fornisce quanto segue quando eseguito:

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

Il codice originale della domanda sarebbe solo:

    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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.