110

When I go to the asyncio page, the first example is a hello world program. When I run it on python 3.73, I can't see any different from the normal one. can anyone tell me the difference and give a non-trivial example?

In [1]: import asyncio
   ...:
   ...: async def main():
   ...:     print('Hello ...')
   ...:     await asyncio.sleep(5)
   ...:     print('... World!')
   ...:
   ...: # Python 3.7+
   ...: asyncio.run(main())
Hello ...
... World!

In [2]:

In [2]: import time
   ...:
   ...: def main():
   ...:     print('Hello ...')
   ...:     time.sleep(5)
   ...:     print('... World!')
   ...:
   ...: # Python 3.7+
   ...: main()
Hello ...
... World!

I intentionally increase the time from 1s to 5s, hope to see something special but I didn't.

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
an offer can't refuse
  • 4,245
  • 5
  • 30
  • 50

2 Answers2

212

You aren't seeing anything special because there's nothing much asynchronous work in your code. However, the main difference is that time.sleep(5) is blocking, and asyncio.sleep(5) is non-blocking.

When time.sleep(5) is called, it will block the entire execution of the script and it will be put on hold, just frozen, doing nothing. But when you call await asyncio.sleep(5), it will ask the event loop to run something else while your await statement finishes its execution.

Here's an improved example.

import asyncio

async def hello():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

async def main():
    await asyncio.gather(hello(), hello())

asyncio.run(main())

Will output:

~$ python3.7 async.py
Hello ...
Hello ...
... World!
... World!

You can see that await asyncio.sleep(1) is not blocking the execution of the script.

In contrast, replacing the line await asyncio.sleep(1) with time.sleep(1), the output will be

Hello ...
... World!
Hello ...
... World!

because time.sleep is blocking and the first call of hello() has to finish first before the second call of hello() starts running.

starball
  • 20,030
  • 7
  • 43
  • 238
Nimeshka Srimal
  • 8,012
  • 5
  • 42
  • 57
  • 24
    some explanation from me which helped me to understand sync vs. async sleep - if you need pause an entire script (supposed to work concurrently) execution, it is sleep. If you need to pause single coroutine, it is asyncio.sleep – Vladimir Kolenov Jun 24 '19 at 06:38
  • 23
    Correction: You wrote "When time.sleep(5) is called, it will block the entire execution of the script ". This is true only for single threaded script. But for the general case this is incorrect. time.sleep(5) blocks only the current thread. – eyalzba Feb 06 '20 at 10:38
  • 3
    @eyalzba yeah, it makes sense. Thank you for adding this comment :) – Nimeshka Srimal Feb 06 '20 at 10:43
  • 16
    This should be the official "hello world" example. – C S Jul 05 '20 at 04:56
  • 2
    Thanks to stackoverflow.com and programmers like you, so many "holes" in standard PY documentation are covered. (One couldn't dig this out from https://docs.python.org/3/library/asyncio.html!) – Apostolos May 27 '21 at 15:05
  • Does anyone know if this experiences drift (topic pointed out throughout this stack overflow post regarding looping a function periodically)? https://stackoverflow.com/questions/474528/what-is-the-best-way-to-repeatedly-execute-a-function-every-x-seconds – LeanMan Jul 18 '21 at 02:44
  • Note that all standard library functions that make a syscall do release the GIL (https://docs.python.org/3/glossary.html#term-GIL), including the `time.sleep` function. Hence you can run the blocking function `time.sleep` function asynchronously, e.g. via `asyncio.to_thread`. See examples: https://stackoverflow.com/a/67122173/2340939 https://stackoverflow.com/a/73706858/2340939 – user2340939 Sep 14 '22 at 12:36
8

With time.sleep(1) below, first, test1() is run every one second, then test2() is run every one second:

import asyncio
import time

async def test1():
    for _ in range(0, 3):
        print('Test1')
        time.sleep(1) # Here
        
async def test2():
    for _ in range(0, 3):
        print('Test2')
        time.sleep(1) # Here
    
async def main():
    await asyncio.gather(test1(), test2()) # Here

asyncio.run(main())

So, 6 seconds are taken to run test1() and test2() in total:

Test1 # 1 second
Test1 # 2 seconds
Test1 # 3 seconds
Test2 # 4 seconds
Test2 # 5 seconds
Test2 # 6 seconds

With asyncio.sleep(1) below, test1() and test2() are run every one second alternately:

import asyncio

async def test1():
    for _ in range(0, 3):
        print('Test1')
        await asyncio.sleep(1) # Here
        
async def test2():
    for _ in range(0, 3):
        print('Test2')
        await asyncio.sleep(1) # Here
    
async def main():
    await asyncio.gather(test1(), test2()) # Here

asyncio.run(main())

So, only 3 seconds are taken to run test1() and test2() in total:

Test1 # 1 second
Test2 # 1 second
Test1 # 2 seconds
Test2 # 2 seconds
Test1 # 3 seconds
Test2 # 3 seconds

And, with time.sleep(0) below, first, test1() is run at once, then test2() is run at once:

import asyncio
import time

async def test1():
    for _ in range(0, 3):
        print('Test1')
        time.sleep(0) # Here
        
async def test2():
    for _ in range(0, 3):
        print('Test2')
        time.sleep(0) # Here
    
async def main():
    await asyncio.gather(test1(), test2()) # Here

asyncio.run(main())

So, 0 second is taken to run test1() and test2() in total:

Test1 # 0 second
Test1 # 0 second
Test1 # 0 second
Test2 # 0 second
Test2 # 0 second
Test2 # 0 second

And, with asyncio.sleep(0) below, test1() and test2() are run at once alternately:

import asyncio

async def test1():
    for _ in range(0, 3):
        print('Test1')
        await asyncio.sleep(0) # Here
        
async def test2():
    for _ in range(0, 3):
        print('Test2')
        await asyncio.sleep(0) # Here
    
async def main():
    await asyncio.gather(test1(), test2()) # Here

asyncio.run(main())

So, only 0 second is taken to run test1() and test2() in total:

Test1 # 0 second
Test2 # 0 second
Test1 # 0 second
Test2 # 0 second
Test1 # 0 second
Test2 # 0 second

Lastly, without time.sleep() or asyncio.sleep() below, first, test1() is run at once, then test2() is run at once:

import asyncio

async def test1():
    for _ in range(0, 3):
        print('Test1')
        
async def test2():
    for _ in range(0, 3):
        print('Test2')
    
async def main():
    await asyncio.gather(test1(), test2()) # Here

asyncio.run(main())

So, 0 second is taken to run test1() and test2() in total:

Test1 # 0 second
Test1 # 0 second
Test1 # 0 second
Test2 # 0 second
Test2 # 0 second
Test2 # 0 second
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129