You can execute a task in a child process with a delay using a wrapper function that sleeps first before executing the target function.
A more elaborate approach can be developed that extends the Process class allowing an arbitrary target function to be executed after a delay. The delayed task can also be canceled by interrupting it before it has started to execute, mimicking the capabilities for the threading.Timer class, but for processes in this case, instead of threads.
In this tutorial, you will discover how to execute a task in a child process with a delay.
Let’s get started.
Need a Delayed Task
We can execute target functions in a new child process using the multiprocessing.Process class.
This requires creating an instance of the class and specifying the target function to execute via the “target” argument.
For example:
1 2 3 |
... # execute the target function in a child process process = Process(target=function) |
Once created, the process can be started by calling the start() method.
For example:
1 2 3 |
... # start the child process process.start() |
This will run the target function in a new child process and leave the caller free to resume other tasks.
If the caller needs to wait for the target function to complete, the join() method on the process can be called which will block until the child process has terminated.
1 2 3 |
... # wait for the child process process.join() |
You can learn more about running functions in a child process in the tutorial:
We can also execute code in a new process by extending the Process class and overriding the run() method.
This method will be called automatically when the start() method of the process is called.
You can learn more about extending the process class in the tutorial:
We may want to run a function in a child process, but wait for a fixed delay before executing the task.
For example, we may want to perform some calculations, but only after a delay of 30 seconds.
How can we execute a target function in a child process with a delay?
Run loops using all CPUs, download your FREE book to learn how.
How to Run a Delayed Task
There are many ways to run a target function in a child process after a delay.
We will explore two approaches, they are:
- Define a custom function to run the function after a delay.
- Develop a custom Timer class.
Let’s take a closer look at both.
Custom Function to Run Target Function After a Delay
We can execute a target function in a child process after a delay by defining a custom function.
The custom function will take the delay in seconds and the function to execute and will call the sleep() function for the given number of seconds before creating a child process and using it to execute the target function.
For example:
1 2 3 4 5 6 7 8 9 10 |
# delayed task function def delayed(interval, function): # wait a while sleep(interval) # execute the target function in a child process process = Process(target=function) # start the child process process.start() # wait for the child process process.join() |
The function can be updated to take arguments for the target function.
The function can also be updated to return the process instead of joining it, allowing the caller to choose what to do while the task is running.
You can learn more about the sleep() function in the tutorial:
Timer Class for Child Processes
Alternatively, we can develop a Timer class.
The threading module provides the threading.Timer class that extends the threading.Thread class and allows a function to be executed after a fixed delay.
You can learn more about the Timer class in the tutorial:
No such class exists in the multiprocessing module for use with processes.
As such, we can develop our own version of the class.
This requires extending the Process class and defining a constructor that takes a delay interval in seconds and a target function to execute (as well as any arguments for the target function).
The run() method of the Process can be overridden to first delay by the interval with a call to sleep, then call the target function.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# execute a target function in a child process after a delay class Timer(Process): def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # execute the target function after a delay def run(self): # wait a while sleep(self._interval) # execute the target function self._function() |
The class can then be updated to offer more features.
A key feature of the threading.Timer class is that the task can be canceled before it is started, e.g. during the delay period.
This can be achieved using an event.
The constructor of the class can define a multiprocessing.Event.
A cancel() method can be added that will set the event.
The run() method can then call the wait() method on the event to wait for the interval in seconds. If the event is set by another process, the run() method will stop waiting. It can then check if the event was set and if so, not call the target function, e.g. cancel.
Put another way, the run() method will only execute the target function if the event is not set.
The updated multiprocessing Timer class with this capability 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 |
# execute a target function in a child process after a delay class Timer(Process): def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # create the event self._event = Event() # execute the target function after a delay def run(self): # wait a while on the event self._event.wait(self._interval) # confirm that the event was not set if not self._event.is_set(): # execute the target function self._function() # set the event so we cannot run it again self._event.set() # cancel the task def cancel(self): self._event.set() |
You can learn more about the multiprocessing.Event in the tutorial:
Now that we know how to execute a target function in a child process after a delay, let’s look at some worked examples.
Example of Executing a Delayed Task in a Child Process
We can explore how to use a custom function to delay the execution of a target function in a child process.
In this example, we will define a custom function named delayed() that takes a delay in seconds and the name of the target function to execute.
This delayed() function will sleep for a given number of seconds, then configure, start and wait for a new child process that executes the target function.
1 2 3 4 5 6 7 8 9 10 |
# delayed task function def delayed(interval, function): # wait a while sleep(interval) # execute the target function in a child process process = Process(target=function) # start the child process process.start() # wait for the child process process.join() |
The delayed() function does not have to wait for the target function. Instead, it could return the process instance and allow the caller to choose what to do while the target function is executing.
We can then define a task to execute after a delay.
In this case, our task will report a message, sleep for 2 seconds to simulate computational effort, then report a final message.
1 2 3 4 5 6 7 8 |
# task to delay def task(): # report a message print('Task is running...', flush=True) # simulate work sleep(2) # report final message print('Task is done', flush=True) |
Finally, we can execute our target task function after a delay from the main process.
1 2 3 4 |
# protect the entry point if __name__ == '__main__': # run the task after a delay delayed(1.0, task) |
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 21 22 23 24 25 26 27 28 29 |
# SuperFastPython.com # example of a delayed multiprocessing task from time import sleep from multiprocessing import Process # delayed task function def delayed(interval, function): # wait a while sleep(interval) # execute the target function in a child process process = Process(target=function) # start the child process process.start() # wait for the child process process.join() # task to delay def task(): # report a message print('Task is running...', flush=True) # simulate work sleep(2) # report final message print('Task is done', flush=True) # protect the entry point if __name__ == '__main__': # run the task after a delay delayed(1.0, task) |
Running the example calls the delayed() function with a delay of one second and the name of the target function.
The delayed() function runs, sleeps for 2 seconds, then executes the target function in a child process.
The main process blocks while the target function runs, by design.
The task reports a message, sleeps for 2 seconds, then reports the final message.
This highlights how we can use a wrapper function or custom function to execute a target function after a delay.
1 2 |
Task is running... Task is done |
Next, let’s explore achieving the same effect using a Timer class.
Free Python Multiprocessing Course
Download your FREE multiprocessing PDF cheat sheet and get BONUS access to my free 7-day crash course on the multiprocessing API.
Discover how to use the Python multiprocessing module including how to create and start child processes and how to use a mutex locks and semaphores.
Example of Multiprocessing Timer Class
We can develop a Timer class to run a target function after a delay using a child process, similar to the threading.Timer class.
In this case, we can keep things simple and assume no arguments to the target function and no ability to cancel the delayed task.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# execute a target function in a child process after a delay class Timer(Process): def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # execute the target function after a delay def run(self): # wait a while sleep(self._interval) # execute the target function self._function() |
We can use the same target task() function as the previous example.
Finally, the main process can create a Timer object, specify a delay and the name of the target function, then start the process and wait for it to complete.
1 2 3 4 5 6 7 8 |
# protect the entry point if __name__ == '__main__': # create the timer timer = Timer(1.0, task) # start the timer timer.start() # wait for he timer to complete timer.join() |
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 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# SuperFastPython.com # example of a multiprocessing timer class for running delayed tasks from time import sleep from multiprocessing import Process # execute a target function in a child process after a delay class Timer(Process): def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # execute the target function after a delay def run(self): # wait a while sleep(self._interval) # execute the target function self._function() # task to delay def task(): # report a message print('Task is running...', flush=True) # simulate work sleep(2) # report final message print('Task is done', flush=True) # protect the entry point if __name__ == '__main__': # create the timer timer = Timer(1.0, task) # start the timer timer.start() # wait for he timer to complete timer.join() |
Running the example first creates an instance of the Timer class with a one-second delay to execute the task() function.
The process is then started and the main process waits for the child process to terminate.
The child process runs, first sleeping for an interval of one second, then executing the target function.
This highlights how we can extend the multiprocessing.Process class to execute a target function after a delay.
1 2 |
Task is running... Task is done |
Next, let’s look at how we might add the ability to cancel a delayed task.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of a Delayed Task That Can Be Canceled
We can explore how to cancel a delayed task using the custom Timer class.
This involves first adding a multiprocessing.Event object to the Timer class constructor.
1 2 3 4 5 6 7 8 |
def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # create the event self._event = Event() |
Next, we can add a cancel() method that will set the event when called.
1 2 3 |
# cancel the task def cancel(self): self._event.set() |
Finally, we can update the run() method to wait for the interval on the event directly.
This will block until either the event is set or the time elapses.
Once the wait is completed, if the event is not set, we can execute the target function and set the event. Otherwise, the task has been canceled and we do not execute the target function.
1 2 3 4 5 6 7 8 9 10 |
# execute the target function after a delay def run(self): # wait a while on the event self._event.wait(self._interval) # confirm that the event was not set if not self._event.is_set(): # execute the target function self._function() # set the event so we cannot run it again self._event.set() |
We set the event after running the function because the task can only be run once. A process class cannot be reused.
You can learn more about the inability to reuse process classes in the tutorial:
Tying this together, the complete example of the updated Timer class that can be canceled 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 |
# execute a target function in a child process after a delay class Timer(Process): def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # create the event self._event = Event() # execute the target function after a delay def run(self): # wait a while on the event self._event.wait(self._interval) # confirm that the event was not set if not self._event.is_set(): # execute the target function self._function() # set the event so we cannot run it again self._event.set() # cancel the task def cancel(self): self._event.set() |
Next, we can explore how to use this class.
First, we can run the normal case to confirm that a target function can still be executed after a delay.
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 42 43 44 45 46 47 48 49 |
# SuperFastPython.com # example of a multiprocessing timer class that can be canceled from time import sleep from multiprocessing import Process from multiprocessing import Event # execute a target function in a child process after a delay class Timer(Process): def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # create the event self._event = Event() # execute the target function after a delay def run(self): # wait a while on the event self._event.wait(self._interval) # confirm that the event was not set if not self._event.is_set(): # execute the target function self._function() # set the event so we cannot run it again self._event.set() # cancel the task def cancel(self): self._event.set() # task to delay def task(): # report a message print('Task is running...', flush=True) # simulate work sleep(2) # report final message print('Task is done', flush=True) # protect the entry point if __name__ == '__main__': # create the timer timer = Timer(1.0, task) # start the timer timer.start() # wait for he timer to complete timer.join() |
Running the example creates the timer class, starts it, then waits for it to complete.
The task runs normally, first delaying for 1 second, then executing the task as we did in the previous section.
1 2 |
Task is running... Task is done |
Next, we can test the cancel case.
In this case, we will start the timer child process, wait a moment during the delay period, then cancel the task.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# protect the entry point if __name__ == '__main__': # create the timer timer = Timer(1.0, task) # start the timer timer.start() # wait a moment sleep(0.5) # cancel the task print(Canceling...') timer.cancel() # wait for the child process to terminate timer.join() |
Tying this together, the complete example of canceling the delayed task 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 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# SuperFastPython.com # example of a multiprocessing timer class that can be canceled from time import sleep from multiprocessing import Process from multiprocessing import Event # execute a target function in a child process after a delay class Timer(Process): def __init__(self, interval, function): # call the parent constructor Process.__init__(self) # store the details self._interval = interval self._function = function # create the event self._event = Event() # execute the target function after a delay def run(self): # wait a while on the event self._event.wait(self._interval) # confirm that the event was not set if not self._event.is_set(): # execute the target function self._function() # set the event so we cannot run it again self._event.set() # cancel the task def cancel(self): self._event.set() # task to delay def task(): # report a message print('Task is running...', flush=True) # simulate work sleep(2) # report final message print('Task is done', flush=True) # protect the entry point if __name__ == '__main__': # create the timer timer = Timer(1.0, task) # start the timer timer.start() # wait a moment sleep(0.5) # cancel the task print(Canceling...') timer.cancel() # wait for the child process to terminate timer.join() |
Running the example first creates the timer class and then starts the child process.
The child process runs and starts the waiting process on the event.
The main process sleeps for half a second then resumes and cancels the task.
This causes the child process to stop waiting immediately and exit the run() method without executing the target function, having the desired effect.
This highlights how we can cancel a delayed task.
1 |
Canceling... |
Further Reading
This section provides additional resources that you may find helpful.
Python Multiprocessing Books
- Python Multiprocessing Jump-Start, Jason Brownlee (my book!)
- Multiprocessing API Interview Questions
- Multiprocessing API Cheat Sheet
I would also recommend specific chapters in the books:
- Effective Python, Brett Slatkin, 2019.
- See: Chapter 7: Concurrency and Parallelism
- High Performance Python, Ian Ozsvald and Micha Gorelick, 2020.
- See: Chapter 9: The multiprocessing Module
- Python in a Nutshell, Alex Martelli, et al., 2017.
- See: Chapter: 14: Threads and Processes
Guides
- Python Multiprocessing: The Complete Guide
- Python Multiprocessing Pool: The Complete Guide
- Python ProcessPoolExecutor: The Complete Guide
APIs
References
Takeaways
You now know how to execute a task in a child process with a delay.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Leon Seibert on Unsplash
Do you have any questions?