You can pause and resume tasks in the ThreadPoolExecutor by using a shared threading.Event.
In this tutorial, you will discover how to pause and resume tasks in the ThreadPoolExecutor.
Let’s get started.
Need to Pause and Resume Tasks
The ThreadPoolExecutor provides a pool of reusable worker threads using the executor design pattern.
Tasks executed in new threads are executed concurrently in Python, making the ThreadPoolExecutor appropriate for I/O-bound tasks.
A ThreadPoolExecutor can be created directly or via the context manager interface and tasks can be issued one-by-one via the submit() method or in batch via the map() method.
For example:
1 2 3 4 5 6 7 |
... # create a thread pool with ThreadPoolExecutor() as tpe: # issue a task future = tpe.submit(task) # the task result once the task is done result = future.result() |
You can learn more about the ThreadPoolExecutor in the tutorial:
When using the ThreadPoolExecutor, we may need to pause all running tasks.
This may be for many reasons, such as:
- The user requests that all tasks pause.
- A shared resource is temporarily unavailable.
- A fault or error has occurred.
Once paused, we may then want to resume all paused tasks again and continue their execution.
How can we pause and resume tasks in the ThreadPoolExecutor?
Run loops using all CPUs, download your FREE book to learn how.
How to Pause and Resume Tasks
The ThreadPoolExecutor does not provide a facility to pause and resume tasks.
Instead, we must develop this capability ourselves.
We can pause and resume tasks in the ThreadPoolExecutor using a threading.Event.
A threading.Event is a thread-safe shared boolean variable. It can be set (True) or cleared (False) and threads can block and wait for the event to be set.
A threading.Event can be used as the center of the capability to pause and resume tasks in the ThreadPoolExecutor.
If you are new to the threading.Event, see the tutorial:
This can be achieved by updating target task functions to frequently check a shared threading.Event and suspending execution until the event is set.
For example:
1 2 3 4 5 |
... # check if the task is paused if not event.is_set(): # wait for the event to be set event.wait() |
Checking first, allows us to handle the request to pause, if needed.
If this is not needed, the wait() method can be called directly. This will only block if the task is to be paused, otherwise it will return immediately.
For example:
1 2 3 |
... # wait for the event to be set event.wait() |
The main thread can then pause tasks on demand by calling the clear() method to unset the event.
For example:
1 2 3 |
... # pause all tasks event.clear() |
Tasks can resume on demand by calling the set() method on the event.
For example:
1 2 3 |
... # resume all tasks event.set() |
And that’s it.
Note, pausing and resuming tasks in the ThreadPoolExecutor is very similar to canceling all running tasks. You can learn more about canceling and stopping tasks in the ThreadPoolExecutor in the tutorial:
Next, let’s look at a worked example of pausing and resuming tasks in the ThreadPoolExecutor.
Example of Pausing and Resuming Tasks
We can explore an example of pausing and resuming tasks in the ThreadPoolExecutor.
In this example we will define a task that does very little, looping 10 times, reporting a message, and blocking for half a second each iteration. We will then issue one of these tasks to the thread pool and let it run. We will then pause the task for a moment, then later resume it and allow it complete.
This same approach can be used to pause and resume many tasks in the thread pool.
Firstly, we can define the task.
The task must take the shared threading.Event and must check the status of the event each iteration. If the event is not set, the task must block and wait for it to be set. This can be achieved by first checking the is_set() method on the event and if not set, calling the wait() method until the event is set.
The task() function below implements this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# task executed in the thread pool def task(event): # main loop of the task for i in range(10): # check if the task is paused if not event.is_set(): # report a message print('Task is PAUSED') # wait for the event to be set event.wait() # report a message print(f'Task is running [{i}]...') # do work sleep(0.5) |
Next in the main thread, we must first create the shared event. We can then set the event so that the task can begin running immediately.
1 2 3 4 5 |
... # create the shared event event = Event() # set the event, allowing tasks to run freely event.set() |
Next, we can create the thread pool and issue the task() function for execution and block while the task begins running.
1 2 3 4 5 6 7 |
... # create the thread pool with ThreadPoolExecutor() as tpe: # issue the task _ = tpe.submit(task, event) # wait a moment sleep(2) |
The main thread can then pause the task by setting the event to False.
This can be achieved via the clear() method.
1 2 3 4 5 6 |
... # pause all tasks print('Main Pausing Tasks') event.clear() # wait a moment sleep(2) |
Finally, the main thread can resume the task by setting the event to true.
This can be achieved via the set() method.
1 2 3 4 |
... # resume print('Main Resuming Tasks') event.set() |
And that’s it.
Tying this together, the complete example of pausing and resuming a task in the ThreadPoolExecutor 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# SuperFastPython.com # example of pausing and resuming tasks in the threadpoolexecutor from concurrent.futures import ThreadPoolExecutor from threading import Event from time import sleep # task executed in the thread pool def task(event): # main loop of the task for i in range(10): # check if the task is paused if not event.is_set(): # report a message print('Task is PAUSED') # wait for the event to be set event.wait() # report a message print(f'Task is running [{i}]...') # do work sleep(0.5) # protect the entry point if __name__ == '__main__': # create the shared event event = Event() # set the event, allowing tasks to run freely event.set() # create the thread pool with ThreadPoolExecutor() as tpe: # issue the task _ = tpe.submit(task, event) # wait a moment sleep(2) # pause all tasks print('Main Pausing Tasks') event.clear() # wait a moment sleep(2) # resume print('Main Resuming Tasks') event.set() |
Running the example first creates the shared event and sets it to True.
The ThreadPoolExecutor is created with the default number of workers using the context manager interface.
The single task is then issued to the thread pool and the main thread blocks, sleeping for two seconds.
The task runs, checking the event each iteration, which is set, reporting a message, and sleeping for half a second.
The main thread resumes and clears the event, setting it to false. The main thread then blocks for two seconds.
The task checks the event and notices that it is cleared. The task then reports a message that it is paused and waits on the event until it is set. The task is paused.
The main thread resumes and sets the event and blocks until all tasks in the thread pool are complete, via the context manager.
The task resumes and completes the iterations of the loop, reporting messages, and sleeps.
This highlights how we can pause and resume tasks in the ThreadPoolExecutor.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Task is running [0]... Task is running [1]... Task is running [2]... Task is running [3]... Main Pausing Tasks Task is PAUSED Main Resuming Tasks Task is running [4]... Task is running [5]... Task is running [6]... Task is running [7]... Task is running [8]... Task is running [9]... |
Free Python ThreadPoolExecutor Course
Download your FREE ThreadPoolExecutor PDF cheat sheet and get BONUS access to my free 7-day crash course on the ThreadPoolExecutor API.
Discover how to use the ThreadPoolExecutor class including how to configure the number of workers and how to execute tasks asynchronously.
Further Reading
This section provides additional resources that you may find helpful.
Books
- ThreadPoolExecutor Jump-Start, Jason Brownlee, (my book!)
- Concurrent Futures API Interview Questions
- ThreadPoolExecutor Class API Cheat Sheet
I also recommend specific chapters from the following books:
- 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 ThreadPoolExecutor: The Complete Guide
- Python ProcessPoolExecutor: The Complete Guide
- Python Threading: The Complete Guide
- Python ThreadPool: The Complete Guide
APIs
References
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Takeaways
You now know how to pause and resume tasks in the ThreadPoolExecutor.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Keagan Henman on Unsplash
Do you have any questions?