Consider the following code:
main.py
import asyncio
import websockets
async def echo(websocket):
async for message in websocket:
await websocket.send(message)
print(message)
async def main():
async with websockets.serve(echo, "localhost", 8765):
await asyncio.Future() # run forever
if __name__ == '__main__':
asyncio.run(main())
other.py
import asyncio
import json
import websockets
tasks = set()
async def run_job(i):
await asyncio.sleep(0.)
print(f"I'm job number {i}")
async def bunch_of_tasks(ws):
for i in range(10):
task = asyncio.create_task(run_job(i), name=f'job-{i}')
tasks.add(task)
task.add_done_callback(tasks.discard)
print(f'had a nice sleep! now my value is {i}')
# await asyncio.sleep(0.)
await ws.send(json.dumps('hello there!'))
await asyncio.gather(*tasks)
print(f'tasks done')
async def do_stuff():
async with websockets.connect("ws://localhost:8765") as websocket:
await bunch_of_tasks(websocket)
await websocket.recv()
if __name__ == '__main__':
asyncio.run(do_stuff())
By first running main.py and then running in parallel other.py, I get:
had a nice sleep! now my value is 0
had a nice sleep! now my value is 1
had a nice sleep! now my value is 2
had a nice sleep! now my value is 3
had a nice sleep! now my value is 4
had a nice sleep! now my value is 5
had a nice sleep! now my value is 6
had a nice sleep! now my value is 7
had a nice sleep! now my value is 8
had a nice sleep! now my value is 9
I'm job number 0
I'm job number 1
I'm job number 2
I'm job number 3
I'm job number 4
I'm job number 5
I'm job number 6
I'm job number 7
I'm job number 8
I'm job number 9
tasks done
But if I uncomment await asyncio.sleep(0.) before await ws.send(json.dumps('hello there!')), I get:
had a nice sleep! now my value is 0
had a nice sleep! now my value is 1
I'm job number 0
had a nice sleep! now my value is 2
I'm job number 1
had a nice sleep! now my value is 3
I'm job number 2
had a nice sleep! now my value is 4
I'm job number 3
had a nice sleep! now my value is 5
I'm job number 4
had a nice sleep! now my value is 6
I'm job number 5
had a nice sleep! now my value is 7
I'm job number 6
had a nice sleep! now my value is 8
I'm job number 7
had a nice sleep! now my value is 9
I'm job number 8
I'm job number 9
tasks done
which is somehow what I would expect.
So apparently sending the message to the web socket does not yield control to the event loop, and the run_job coroutine does not have the opportunity to be run. However, asyncio.sleep effectively suspends the current task and gives an opportunity to run_job to be executed.
Why that happens?