Last Updated on September 12, 2022
You can handle unexpected exceptions in a thread by using an excepthook.
In this tutorial you will discover how to handle unexpected exceptions in Python threads
Let’s get started.
Need to Handle Unexpected Exceptions in Threads
A thread is a thread of execution in a computer program.
Every Python program has at least one thread of execution called the main thread. Both processes and threads are created and managed by the underlying operating system.
Sometimes we may need to create additional threads in our program in order to execute code concurrently.
Python provides the ability to create and manage new threads via the threading module and the threading.Thread class.
It is possible for new threads to raise an unexpected exception while executing. This has a catastrophic effect on the new thread, unwinding the stack of function calls and ultimately stopping the thread from running any further.
Ideally, we would like to know when an unexpected exception occurs in new threads so that we might take appropriate action to clean-up any resources and perhaps log the fault.
How can we handle unexpected exceptions in new Python threads?
Run loops using all CPUs, download your FREE book to learn how.
How to Handle Unexpected Exceptions in Threads
We can specify how to handle unhandled errors and exceptions that occur within new threads via the exception hook, referred to as “excepthook“.
By default, there is no exception hook, in which case the sys.excepthook function is called that reports the familiar message.
We can specify a custom exception hook function that will be called whenever a threading.Thread fails with an unhandled Error or Exception.
This can be achieved via the threading.excepthook function.
First, we must define a function that takes a single argument that will be an instance of the ExceptHookArgs class, containing details of the exception and thread.
1 2 3 |
# custom exception hook def custom_hook(args): # ... |
We can then specify the exception hook function to be called whenever an unhandled exception bubbles up to the top level of a thread.
1 2 3 |
... # set the exception hook threading.excepthook = custom_hook |
And that’s all there is to it.
Note, this requires Python v3.8 or higher.
Now that we know how to handle unexpected exceptions in Python threads, let’s look at some worked examples.
Example of an Unhandled Exception in a Thread
An unhandled exception can occur in a new thread.
The effect will be that the thread will unwind and report the message on standard error.
Unwinding the thread means that the thread will stop executing at the point of the exception (or error) and that the exception will bubble up the stack in the thread until it reaches the top level, e.g. the run() function.
We can demonstrate this with an example.
Firstly, we can define a target function that will block for a moment then raise an exception.
1 2 3 4 5 6 |
# target function that raises an exception def work(): print('Working...') sleep(1) # rise an exception raise Exception('Something bad happened') |
Next, we can create a thread that will call the target function and wait for the function to complete.
1 2 3 4 5 6 7 8 9 |
... # create a thread thread = Thread(target=work) # run the thread thread.start() # wait for the thread to finish thread.join() # continue on print('Continuing on...') |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# SuperFastPython.com # example of an unhandled exception in a thread from time import sleep from threading import Thread # target function that raises an exception def work(): print('Working...') sleep(1) # rise an exception raise Exception('Something bad happened') # create a thread thread = Thread(target=work) # run the thread thread.start() # wait for the thread to finish thread.join() # continue on print('Continuing on...') |
Running the example creates the thread and starts executing it.
The thread blocks for a moment and raises an exception.
The exception bubbles up to the run() function and is then reported on standard error (e.g. printed out).
Importantly, the failure in the thread does not impact the main thread, which continues on executing.
1 2 3 4 5 6 7 8 |
Working... Exception in thread Thread-1: Traceback (most recent call last): ... exception_unhandled.py", line 11, in work raise Exception('Something bad happened') Exception: Something bad happened Continuing on... |
Next, let’s look at how we might update the example to use an excepthook to handle unexpected exceptions in threads.
Free Python Threading Course
Download your FREE threading PDF cheat sheet and get BONUS access to my free 7-day crash course on the threading API.
Discover how to use the Python threading module including how to create and start new threads and how to use a mutex locks and semaphores
Example of Using a Thread Exception Hook
In this section we can explore how to use the exception hook to handle an unexpected exception.
We can update the example in the previous section to use an excepthook.
First, we can define a custom hook function that reports a single line to standard out.
1 2 3 4 |
# custom exception hook def custom_hook(args): # report the failure print(f'Thread failed: {args.exc_value}') |
We can then configure the threading module to call our function whenever any thread raises an unexpected exception.
1 2 3 |
... # set the exception hook threading.excepthook = custom_hook |
And that’s it.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# SuperFastPython.com # example of an unhandled exception in a thread from time import sleep import threading # target function that raises an exception def work(): print('Working...') sleep(1) # rise an exception raise Exception('Something bad happened') # custom exception hook def custom_hook(args): # report the failure print(f'Thread failed: {args.exc_value}') # set the exception hook threading.excepthook = custom_hook # create a thread thread = threading.Thread(target=work) # run the thread thread.start() # wait for the thread to finish thread.join() # continue on print('Continuing on...') |
Running the example creates and runs the thread.
The new thread blocks for a moment, then fails with an exception.
The exception bubbles up to the top level of the thread at which point the custom exception hook function is called. Our custom message is then reported.
The main thread then continues to execute as per normal.
This can be helpful if we want to perform special actions when a thread fails unexpectedly, such as log to a file.
1 2 3 |
Working... Thread failed: Something bad happened Continuing on... |
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Further Reading
This section provides additional resources that you may find helpful.
Python Threading Books
- Python Threading Jump-Start, Jason Brownlee (my book!)
- Threading API Interview Questions
- Threading Module API Cheat Sheet
I also recommend specific chapters in the following books:
- Python Cookbook, David Beazley and Brian Jones, 2013.
- See: Chapter 12: Concurrency
- Effective Python, Brett Slatkin, 2019.
- See: Chapter 7: Concurrency and Parallelism
- Python in a Nutshell, Alex Martelli, et al., 2017.
- See: Chapter: 14: Threads and Processes
Guides
- Python Threading: The Complete Guide
- Python ThreadPoolExecutor: The Complete Guide
- Python ThreadPool: The Complete Guide
APIs
References
Takeaways
You now know how to handle exceptions in threads.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by DEAN FAULKNER on Unsplash
Do you have any questions?