How To Make Python Wait

Posted by
on under

For many types of applications, at times it is necessary to pause the running of the program until some external condition occurs. You may need to wait until another thread finishes, or maybe until a new file appears in a directory on disk that is being watched.

In these and many other situations you will need to figure out a way to make your script wait, and this isn't as easy as it sounds if you want to do it properly! In this article I'm going to show you a few different ways to wait. I'm going to use Python for all the examples, but the concepts I'm going to present apply to all programming languages.

An Example Application That Waits

To show you these wait patterns, I'm going to use an example application, shown below:

from random import random
import threading
import time

result = None

def background_calculation():
    # here goes some long calculation
    time.sleep(random() * 5 * 60)

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42

def main():
    thread = threading.Thread(target=background_calculation)
    thread.start()

    # TODO: wait here for the result to be available before continuing!

    print('The result is', result)

if __name__ == '__main__':
    main()

In this application, the background_calculation() function performs some computation that is slow. To keep this example simple, I have coded this function with a time.sleep() call with a random time of up to 5 minutes. When the function reaches the end, a result global variable is set with the result of this made-up calculation, which is going to be, obviously, the number forty-two.

The main application function starts the background calculation in a separate thread, then waits for the thread to complete its work and finally prints the result global variable. The version of this function that you see above does not have the waiting part implemented, you can see a TODO comment in the place where the wait needs to take place. In the following sections I'm going to show you a few different ways to implement this wait, starting from the worst and working my way up to the best.

The Ugly: Busy Waiting

The easiest and most intuitive way to perform this wait is to use a while-loop:

    # wait here for the result to be available before continuing
    while result is None:
        pass

If you want to try this, here is the complete script that you can copy/paste:

from random import random
import threading
import time

result = None

def background_calculation():
    # here goes some long calculation
    time.sleep(random() * 5 * 60)

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42

def main():
    thread = threading.Thread(target=background_calculation)
    thread.start()

    # wait here for the result to be available before continuing
    while result is None:
        pass

    print('The result is', result)

if __name__ == '__main__':
    main()

This is a really bad way to wait. Can you tell why?

If you want to experience it, you can try the script on your system. As soon as the script runs, open your Task Manager on Windows, Activity Monitor on Mac or maybe top if you prefer the command line. Look at CPU usage and note how it goes through the roof.

This while-loop appears to be an empty loop, and in fact it mostly is, with the only exception that the loop exit condition needs to be checked over and over again to determine when the loop should exit. So while the loop body is completely empty, Python is forced to continuously evaluate result is None, and actually, the fact that the loop is empty makes Python concentrate fully on repeating this evaluation as fast as it possibly can, burning a lot of CPU cycles, and making everything else running on that CPU much slower!

This type of wait loop is often called a busy wait. And a CPU that is stuck doing a lot of work over nothing as in this case is said to be spinning. Never do this.

The Bad: Busy Waiting With Sleep

It is interesting that in the busy waiting example from the previous section, you would think that having an empty loop should give less work to the CPU, but in fact the contrary happens. So the obvious improvement to the previous solution is to add something inside the while-loop that puts a brake to the CPU frantically evaluating the while-loop exit condition.

I'm sure a lot of you can guess what we can do inside the loop to slow things down a bit. We can sleep:

    # wait here for the result to be available before continuing
    while result is None:
        time.sleep(15)

Here is the entire script, in case you want to run it locally:

from random import random
import threading
import time

result = None

def background_calculation():
    # here goes some long calculation
    time.sleep(random() * 5 * 60)

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42

def main():
    thread = threading.Thread(target=background_calculation)
    thread.start()

    # wait here for the result to be available before continuing
    while result is None:
        time.sleep(15)

    print('The result is', result)

if __name__ == '__main__':
    main()

The time.sleep() function will suspend the execution for the number of seconds passed in the argument. In the above example, we are sleeping for 15 seconds in each loop iteration, which means that Python will only evaluate the exit condition of the loop at a rate of four times per minute, compared to as fast as it could in the previous version. During those 15 seconds of sleep the CPU will not perform any work, and will be free to take on work from other processes running on your computer.

If you try this version of the wait, you are going to find that the script waits without overtasking the CPU, so you may think that we now have the perfect solution. Yet, I have titled this section "The Bad", didn't I?

While this solution is miles better than the previous one, there are two problems with it that still make it less than ideal. First of all, this loop still qualifies as busy waiting. It uses a lot less CPU than the previous one, but we still have a CPU that is spinning. We just made it tolerable by reducing the frequency of the spin.

The second problem is more concerning, in my opinion. Imagine the background task that is doing this calculation takes exactly 61 seconds to complete its work and produce a result. If our wait loop starts at about the same time the task starts, it would be checking the value of the result variable at 0, 15, 30, 45, 60 and 75 seconds. The check at 60 seconds would still return False because the background task still has one more second to go, so it will be the check at 75 seconds the causes the loop to exit. Can you see the problem? The loop exited at 75 seconds, but the background task finished at 61, so the wait extended for an extra 14 seconds!

While this type of wait is very common, it has this "resolution" problem, where the length of the wait is a multiple of the amount of sleep you do inside the loop. If you sleep less, then your wait time will be more accurate, but your CPU usage will go up due to busy waiting. If you sleep more, then you use less CPU, but you may end up waiting much longer than needed.

The Good #1: Joining the Thread

Let's say that we want our wait to be as efficient as possible. We want the wait to be over at the exact moment the result is produced by the calculation thread. How can we do that?

Solutions implemented just with Python logic like the previous two are not going to work, because to determine if the thread finished we need to run some Python code. If we run the check too often we use a lot of CPU, and if we run it not too often we will miss the exact moment the thread completed. We've seen this clearly in the previous two sections.

To be able to wait efficiently, we need external help from the operating system, which can efficiently notify our application when certain events occur. In particular, it can tell us when a thread exits, an operation that is called joining a thread.

The threading.Thread class from the Python standard library has a join() method that will return at the exact moment the thread exits:

    # wait here for the result to be available before continuing
    thread.join()

And here is the complete script:

from random import random
import threading
import time

result = None

def background_calculation():
    # here goes some long calculation
    time.sleep(random() * 5 * 60)

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42

def main():
    thread = threading.Thread(target=background_calculation)
    thread.start()

    # wait here for the result to be available before continuing
    thread.join()

    print('The result is', result)

if __name__ == '__main__':
    main()

The join() call blocks in the same way as time.sleep(), but instead of blocking for a fixed amount of time, it is going to block while the background thread runs. At the exact moment the thread finishes, the join() function is going to return, and the application can continue. The operating system makes doing an efficient wait a lot easier!

The Good #2: Waiting on an Event

If you need to wait for a thread to finish, the pattern I presented in the previous section is what you should use. But of course, there are many other situations in which you may need to wait for things other than threads, so how do you wait for some sort of ordinary event not tied to a thread or other operating system resource?

To show you how to do this, I'm going to modify the background thread in the example I've been using and make a bit more complex. This thread is still going to produce a result, but it is not going to exit immediately after that, it will continue running and doing some more work:

from random import random
import threading
import time

result = None

def background_calculation():
    # here goes some long calculation
    time.sleep(random() * 5 * 60)

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42

    # do some more work before exiting the thread
    time.sleep(10)

def main():
    thread = threading.Thread(target=background_calculation)
    thread.start()

    # wait here for the result to be available before continuing
    thread.join()

    print('The result is', result)

if __name__ == '__main__':
    main()

If you run the above version of the example, the result is going to be reported 10 seconds late, because the thread remains running for that long after generating the result. But of course we want to report the result at the exact moment it is available.

For situations like these, where you need to wait on an arbitrary condition, we can use an Event object, which comes in the threading package from the Python standard library. Here is how to create an event:

result_available = threading.Event()

Events have a wait() method, which we will use to write our wait:

    # wait here for the result to be available before continuing
    result_available.wait()

The difference between the Event.wait() and Thread.join() methods is that the latter is pre-programmed to wait for a specific event, which is the end of a thread. The former is a general purpose event that can wait on anything. So if this event object can wait on any condition, how do we tell it when to end the wait? For that, the event object has a set() method. Immediately after the background thread sets the result global variable it can set the event, causing any code waiting on it to unblock:

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42
    result_available.set()

Here is the complete code for this example:

from random import random
import threading
import time

result = None
result_available = threading.Event()

def background_calculation():
    # here goes some long calculation
    time.sleep(random() * 5 * 60)

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42
    result_available.set()

    # do some more work before exiting the thread
    time.sleep(10)

def main():
    thread = threading.Thread(target=background_calculation)
    thread.start()

    # wait here for the result to be available before continuing
    result_available.wait()

    print('The result is', result)

if __name__ == '__main__':
    main()

So here you can see how the background thread and the main thread are synchronized around this Event object.

The Good #3: Waiting While Displaying a Progress Percentage

One great thing about event objects is that they are general purpose, so you are going to find a lot of situations in which they can be useful if you apply a little bit of creativity. For example, consider this common pattern when writing a background thread function:

exit_thread = False

def background_thread():
    while not exit_thread:
        # do some work
        time.sleep(10)

Here we are trying to write a thread that can be terminated gracefully by setting the exit_thread global variable to True. This is a pretty common pattern, but by now you can probably identify why this isn't a great solution, right? It can take up to 10 seconds from the time the exit_thread variable is set until the thread actually exits, and that is without counting the extra time that may pass until the thread reaches the sleep statement.

We can write this in a much more efficient way using an Event object, taking advantage of the timeout argument that can be passed into the Event.wait() method:

exit_thread = threading.Event()

def background_thread():
    while True:
        # do some work
        if exit_thread.wait(timeout=10):
            break

With this implementation we have replaced a fixed-time sleep with a smart wait on an event object. We are still sleeping for 10 seconds at the end of each iteration, but if the thread is stuck in the exit_thread.wait(timeout=10) call at the exact moment the event's set() method is called from somewhere else, then the call will promptly return True and the thread will exit. If the timeout of 10 seconds is reached, then the wait() call returns False and the thread continues running the loop, so it is the same result as calling time.sleep(10).

If some other part of the program calls exit_thread.set() at a time where the thread is doing some work inside the loop, then the thread will continue running, but as soon as it reaches the exit_thread.wait() call it will return True immediately and exit. The secret to be able to terminate the thread without having to wait a lot is to make sure the event object is checked often enough.

Let me show you one more complete example using this timeout argument. What I'm going to do is take the code from the previous section and expand it to show a completion percentage while the wait is taking place.

First, let's add progress reporting to our background thread. In the original version, I slept for a random number of seconds up to a maximum of 300, which is 5 minutes. To report task progress during this time I'm going to replace the single sleep with a loop that runs 100 iterations sleeping a little bit in each, and this will give me the opportunity to report a progress percentage in each iteration. Since the big sleep went for up to 300 seconds, now I'm going to do 100 sleeps of up to 3 seconds each. Overall, this task will take the same amount of random time, but having the work partitioned in 100 pieces makes it easy to report a completion percentage.

Here are the changes to the background thread to report progress percentages in a progress global variable:

progress = 0

def background_calculation():
    # here goes some long calculation
    global progress
    for i in range(100):
        time.sleep(random() * 3)
        progress = i + 1

    # ...

And now we can build a more intelligent wait that reports the percentage of completion every 5 seconds:

    # wait here for the result to be available before continuing
    while not result_available.wait(timeout=5):
        print('\r{}% done...'.format(progress), end='', flush=True)
    print('\r{}% done...'.format(progress))

This new while loop is going to wait for the result_available event for up to 5 seconds as an exit condition. If nothing happens during this interval, then wait() is going to return False and we get inside the loop, where the current value of the progress variable is printed. Note that I use the \r character and the end='', flush=True arguments to the print() function to prevent the terminal from jumping to the next line. This trick allows you to print and reprint the same terminal line, so each progress line will print on top of the previous one.

As soon as the background calculation calls set() on the event object the loop is going to exit because wait() will immediately return True, and at this point I issue one more print, this time with the default end of line, so that I get the final percentage printed and the terminal is left ready to print the result on the next line.

Here is the complete code, if you want to run it or study it in more detail:

from random import random
import threading
import time

progress = 0
result = None
result_available = threading.Event()

def background_calculation():
    # here goes some long calculation
    global progress
    for i in range(100):
        time.sleep(random() * 3)
        progress = i + 1

    # when the calculation is done, the result is stored in a global variable
    global result
    result = 42
    result_available.set()

    # do some more work before exiting the thread
    time.sleep(10)

def main():
    thread = threading.Thread(target=background_calculation)
    thread.start()

    # wait here for the result to be available before continuing
    while not result_available.wait(timeout=5):
        print('\r{}% done...'.format(progress), end='', flush=True)
    print('\r{}% done...'.format(progress))

    print('The result is', result)

if __name__ == '__main__':
    main()

More Ways to Wait!

Event objects are not the only way to wait for events in your application, there are more ways, some of which may be more appropriate than events, depending on what you are waiting for.

If you need to watch a directory for files and act on the files as they are dropped there or when existing files are modified, an event is not going to be useful because the condition that should set the event is external to the application. In this case you need to use facilities provided by the operating system to watch for file system events. In Python, you can use the watchdog package, which wraps a few file watching APIs available in different operating systems.

If you need to wait for a subprocess to end, the subprocess package provides some functions to launch and wait for processes.

If you need to read from a network socket, the default configuration of the socket will make your read block until data arrives, so this works well as an efficient wait. If you need to wait on data arriving on multiple sockets or other file descriptors, then the select package from the Python standard library contains wrappers for operating system functions that can do this waiting efficiently.

If you want to write applications that produce and/or consume items of data, then you can use a Queue object. The producer adds items to the queue, while the consumer efficiently waits for items to take from it.

As you can see, in most cases, the operating system provides efficient wait mechanisms, so all you need to do is find how to access those from Python.

Waiting in Asyncio

If you are using the asyncio package, then you have access to similar types of waiting functions. For example, there are asyncio.Event and asyncio.Queue objects that are modeled after the original ones in the standard library, but based on the async/await style of programming.

Conclusion

I hope this article motivates you to think more carefully about how you wait in your applications. I suggest you play with all the examples I provided to familiarize with these techniques and eventually use them to replace inefficient time.sleep() calls in your code!

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

36 comments
  • #1 Viktor said

    I noticed in your full example of syncing on a result_available Event object, you explicitly declared result as a global in your background task method, but you didn't declare the result_available object as global. Can you please explain why?

  • #2 Miguel Grinberg said

    @Viktor: declaring a global variable is only necessary if you want to modify it. If you don't declare it, then Python will create a local variable of the same name. For reading, however, if the variable isn't found in the local scope, then Python goes to the outer scope and looks there, so global variables can be read without declaring them as global.

  • #3 Jair said

    This is well-written and very helpful! Thanks!

  • #4 Affa said

    Another piece of great work.
    Well explained example.

  • #5 bryan said

    What if the event I want my app to wait for is a trigger from a serial port? Is there a flask-compatible package your recommend for python?
    Essentially, I have a flask template that I want to render, then wait of a serial port trigger. Once the input from the serial port is received, I'd like to quickly record the time the event occurred, and redirect to the next endpoint.

    can you recommend a way of doing this? Should I look into integrating node-serialport into my flask app?

  • #6 Miguel Grinberg said

    @bryan: reading a serial port is a waitable operation. For example, with pyserial you can call the read()` method and that will return exactly when data shows up. Is this what you are trying to do or did I misinterpret your description?

  • #7 Alvaro said

    Very useful and well explained, thanks!

  • #8 Vinod said

    Well written & explained

  • #9 Avi said

    Great Explanation ! Well Done !

  • #10 Pedro Rivera said

    Very bad advises, why just dont use concurrent.futures or asyncio structures, way better in all kinds, also, python and threading?, Thats the 101 how not to go concurrent, this is so 2008. Follow this and you will have hard to mantain and totally slow implementations

  • #11 Miguel Grinberg said

    @Pedro: I don't think you get the point of this article. If I wanted to show people how to run a background task I would have included an example showing how to do that with thread or process executors. But this isn't about that, it is about waiting. How does an executor help if you need to wait for a file to appear on your disk, for example? Or if you want to show a progress bar while waiting for a background task to complete?

  • #12 karl said

    Thank from a real newbie

  • #13 Manuel Souto Pico said

    I have tried your "good" approaches above, but they don't work inside my on_created method in my Event(LoggingEventHandler) class. I want to post a file after it is completely copied to the folder, not when it first appers (incomplete). Your approaches above don't send to make the method wait, it just jumps over the thread.join() and goes directly to post the file. Could it be because the threading.Thread is being used inside another thread? Thanks for any tips.

  • #14 Miguel Grinberg said

    @Manuel: you need to show me the code that you are using, I can diagnose the problem without seeing the code.

  • #15 Igor Sousa Silva said

    Hello there, I would like to know how can I do if I want to wait more than one button in Tkinter, like, I have the button "Add" and "Exclude", if one is pressed, return to the point that I was before waiting. I googled it but every answer is having both pressed, one first then the rest of the buttons. In my case is one OR another. I have on my code something like this

    message_str = "text\n" + str_variable
    label_3.config(text=message_str)
    runApp.wait_variable(var)

    runApp is a button:

    runApp = tk.Button(..., command=lambda: var.set(1))

    everything works fine, is just some improvement so that the user doesn't need to click this button everytime, I have 2 more buttons, where in some cases I could already return to the function that was running.

    very good explanation btw

  • #16 Miguel Grinberg said

    @Igor: can you program your buttons to set an event, and then instead of wait on a variable you wait on the event as shown in this article?

  • #17 Igor Sousa Silva said

    Sorry, i thought that I would receive something in my e-mail, I didn't see your answer.
    So, I can, but I'm not seeing how this is going to help me, the thing is, I have my main program running, a text is shown to the user, he can add the text to one class or pass that text to another, so that he can decide after where to put the text. So I have two options here... and my entire program waits to the user's decision, ADD or PASS

  • #18 Miguel Grinberg said

    @Igor: I'm sorry but I don't understand your logic. Your tkinter program is not waiting on your user's decision, it is waiting on an event loop, yes? When a button is pressed, an event handler that you provided for the button is called. That's all tkinter does.

    Now consider you have your Python logic in a separate thread, and here you wait on an event object. The tkinter event handlers for your buttons all do what they need to do, and then set the event object. Your thread then will be doing exactly what you want, which is to block while it waits for any of the buttons to be pressed, at which point the event will be released and your thread will continue.

  • #19 Maanas said

    This was very helpful to me! I didn't go through the entire "thread" (pun intended), but I did reach "The Good #2: Waiting on an Event". Thank you.

  • #20 Thierry Caiheton said

    Thanks for that tuto.
    I have problems using touchdesigner (scripting python) waiting events and your article is simple and clear.
    I believe it will help me a lot.
    Thansks very much !

  • #21 Jeff said

    Great stuff - thanks!!

  • #22 Bennett Gould said

    Miguel, I am trying to use this information, but a bit lost. I have written a web crawler that sends an SMTP message once a certain element on a page has changed. I have a loop that checks the page then waits 60 seconds. How would I use threading? Would I join the thread, use events, or do I need something outside the scope of this article?

  • #23 Miguel Grinberg said

    @Bennett: if all you need to do is wait 60 seconds, then nothing is simpler and more efficient than time.sleep(60). The examples in this article are all for waiting for some event, not for waiting a fixed amount of time.

  • #24 ak said

    Thanks for the writeup.
    I am trying to implement "The Good #3" by subclassing the Process and using custom defined method "end_process" to terminate the process after receiving the event. But the event is never received within this custom-defined method but if am listening to the event within run() then the event is received.
    Can you please suggest?

    import threading
    import time
    from multiprocessing import Process
    
    class InitThread(threading.Thread):
        def __init__(self, event):
            threading.Thread.__init__(self)
            print('Init thread')
            self.event = event
    
        def run(self) -> None:
            print('Runnning thread')
            self.daemon_thread()
    
        def daemon_thread(self):
            time.sleep(5)
            print('Sending Signal')
            self.event.set()
            print('Wait another 5 sec')
            time.sleep(5)
            print('daemon_thread Exiting')
    
    class InitProcess(Process):
        def __init__(self):
            Process.__init__(self)
            self.event = threading.Event()
            self.received_terminate = False
    
        def run(self) -> None:
            d = InitThread(self.event)
            d.setDaemon(True)
            print('Starting Thread', d)
            d.start()
            print("Event id", id(self.event))
            # while not self.event.wait(timeout=2):
            #     print(f'\rNotification not received \n', end='', flush=True)
            d.join()
            print('Terminating process')
    
        def end_process(self):
            print('Received Terminate')
            print("Event id", id(self.event))
            while not self.event.wait(timeout=2):
                print(f'\rNotification not received \n', end='', flush=True)
            self.terminate()
            print("Terminating...")
    
    
    if __name__ == '__main__':
        p = InitProcess()
        p.daemon = True
        time.sleep(1.0)
        p.start()
        print("Don't wait. Do some other work")
        time.sleep(3)
    
        print("End the process")
        p.end_process()
        # p.terminate()
        print('Process status ', p.is_alive())
        time.sleep(5)
        print('Process status ', p.is_alive())
        print('End main')
    
  • #25 Miguel Grinberg said

    @ak: I'm not sure I understand what you are trying to do. You are calling the set() method inside your thread, but you are not exiting the thread right after. Also I'm lost on why you call set() inside the thread, instead of when you want the thread and process to exit.

Leave a Comment