Catch a thread's exception in the caller thread? Catch a thread's exception in the caller thread? python python

Catch a thread's exception in the caller thread?


The problem is that thread_obj.start() returns immediately. The child thread that you spawned executes in its own context, with its own stack. Any exception that occurs there is in the context of the child thread, and it is in its own stack. One way I can think of right now to communicate this information to the parent thread is by using some sort of message passing, so you might look into that.

Try this on for size:

import sysimport threadingimport Queueclass 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:            breakif __name__ == '__main__':    main()


There are a lot of really weirdly complicated answers to this question. Am I oversimplifying this, because this seems sufficient for most things to me.

from threading import Threadclass 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

If you're certain you'll only ever be running on one or the other version of Python, you could reduce the run() method down to just the mangled version (if you'll only be running on versions of Python before 3), or just the clean version (if you'll only be running on versions of Python starting with 3).

Example usage:

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

And you'll see the exception raised on the other thread when you join.

If you are using six or on Python 3 only, you can improve the stack trace information you get when the exception is re-raised. Instead of only the stack at the point of the join, you can wrap the inner exception in a new outer exception, and get both stack traces with

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

or

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


The concurrent.futures module makes it simple to do work in separate threads (or processes) and handle any resulting exceptions:

import concurrent.futuresimport shutildef 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 is included with Python 3.2, and is available as the backported futures module for earlier versions.