Issue
I have this code:
import asyncio
async def part1():
print('1')
await asyncio.sleep(2)
print('5')
async def part2():
print('2')
await asyncio.sleep(2)
print('6')
async def main():
p1 = await part1()
p2 = await part2()
print('3')
print('4')
asyncio.run(main())
When I run it, I would expect to print
1
2
3
4
5
6
The way I understand it the thread should go like this:
- Starts main() function
- Starts part1() function
- Prints 1
- While it's waiting 2 seconds, it should continue on the line p2 = await part2()
- Starts part2() function
- Prints 2
- While it's waiting for 2 seconds, it should continue on the line of main() "print('3')", then "print('4')"
- part1() function ends its sleep, so it prints 5
- part2() function ends its sleep, so it prints 6
However, what it prints is:
1
5
2
6
3
4
And waits the full time for both async.sleep(2)
What am I missing here?
Thanks!
Solution
In response to your comment:
... [T]he way I understand it is that when I call
main()
viaasyncio.run()
it creates a sort of 'wrapper' or object that keeps track of the thread and tries to keep the calculations running when an asynchronous function is idle
(Small disclaimer - pretty much all my experience with async stuff is in C#, but the await
keywords in each seem to match pretty well in behavior)
Your understanding there is more or less correct - asyncio.run(main())
will start main()
in a separate (background) "thread".
(Note: I'm ignoring the specifics of the GPL and python's single-threadedness here. For the sake of the explanation, a "separate thread" is sufficient.)
The misunderstanding comes in with how you think await
works, how it actually works, and how you've arranged your code. I haven't really been able to find a sufficient description of how Python's await
works, other than in the PEP that introduced it:
await, similarly to yield from, suspends execution of read_data coroutine until db.fetch awaitable completes and returns the result data.
On the other hand, C#'s await
has a lot more documentation/explanation associated with it:
When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
In your case, you've preceded the "middle output" (3 & 4) with 2 awaits, both of which will return control to the asycnio.run(...)
until they have a result.
Here's the code I used that gives me the result you're looking for:
import asyncio
async def part1():
print('1')
await asyncio.sleep(2)
print('5')
async def part2():
print('2')
await asyncio.sleep(2)
print('6')
async def part3():
print('3')
print('4')
async def main():
t1 = asyncio.create_task(part1())
t2 = asyncio.create_task(part2())
t3 = asyncio.create_task(part3())
await t1
await t2
await t3
asyncio.run(main())
You'll notice I turned your main
into my part3
and created a new main
. In the new main
, I create a separate awaitable Task
for each part (1, 2, & 3). Then, I await
them in sequence.
When t1
runs, it hits an await
after the first print. This pauses part1
at that point until the awaitable completes. Program control will return to the caller (main
) until that point, similar to how yield
works.
While t1
is "paused" (waiting), main
will continue on and start up t2
. t2
does the same thing as t1
, so startup of t3
will follow shortly after. t3
does no await
-ing, so its output occurs immediately.
At this point, main
is just waiting for its child Task
s to finish up. t1
was await
-ed first, so it will return first, followed shortly by t2
. The end result is (where test.py
is the script I put this in):
~/.../> py .\test.py
1
2
3
4
5
6
Answered By - b_c
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.