[英]Understanding AsyncIO and order of thread with simple example
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:我理解它的方式线程应该是这样的:
However, what it prints is:但是,它打印的是:
1
5
2
6
3
4
And waits the full time for both async.sleep(2)并等待 async.sleep(2) 的全部时间
What am I missing here?我在这里缺少什么?
Thanks!谢谢!
- While it's waiting 2 seconds, it should continue当它等待 2 秒时,它应该继续
This is a misunderstanding.这是一种误解。 await
means precisely the opposite, that it should not continue (running that particular coroutine) until the result is done. await
方法正好相反,它不应该继续(运行特定协程),直到结果完成。 That's the "wait" in "await".这就是“等待”中的“等待”。
If you want to continue, you can use:如果要继续,可以使用:
# spawn part1 and part2 as background tasks
t1 = asyncio.create_task(part1())
t2 = asyncio.create_task(part2())
# and now await them while they run in parallel
p1 = await t1
p2 = await t2
A simpler way to achieve the same effect is via the gather
utility function:实现相同效果的更简单方法是通过gather
实用程序函数:
p1, p2 = asyncio.gather(part1(), part2())
Note that the code modified like this still won't output 1 2 3 4 5 6
because the final prints will not be executed until the tasks finish.请注意,像这样修改的代码仍然不会输出1 2 3 4 5 6
因为在任务完成之前不会执行最终打印。 As a result, the actual output will be 1 2 5 6 3 4
.因此,实际输出将为1 2 5 6 3 4
。
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 ... [T]他的理解方式是,当我通过asyncio.run()
调用main()
,它会创建一种“包装器”或对象,用于跟踪线程并尝试在异步函数空闲
(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) (小免责声明 - 我对异步内容的几乎所有经验都在 C# 中,但每个中的await
关键字似乎在行为上都非常匹配)
Your understanding there is more or less correct - asyncio.run(main())
will start main()
in a separate (background) "thread".您的理解或多或少是正确的 - asyncio.run(main())
将在单独的(后台)“线程”中启动main()
。
( 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. ) (注意:我在这里忽略了 GPL 的细节和 python 的单线程性。为了解释起见,“单独的线程”就足够了。 )
The misunderstanding comes in with how you think await
works, how it actually works, and how you've arranged your code.误解来自于您认为await
工作方式、实际工作方式以及您如何安排代码。 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:除了在介绍它的PEP中之外,我真的无法找到关于 Python 的await
如何工作的充分描述:
await, similarly to yield from, suspends execution of read_data coroutine until db.fetch awaitable completes and returns the result data. await 与 yield from 类似,暂停 read_data 协程的执行,直到 db.fetch awaitable 完成并返回结果数据。
On the other hand, C#'s await
has a lot more documentation / explanation associated with it:另一方面,C# 的await
有更多与之相关的文档/解释:
When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.当应用 await 关键字时,它会挂起调用方法并将控制权交还给调用者,直到等待的任务完成。
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.在您的情况下,您已经在“中间输出”(3 和 4)之前进行了 2 次等待,这两个等待都会将控制权返回给asycnio.run(...)
直到得到结果。
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
.你会注意到我把你的main
变成了我的part3
并创建了一个新的main
。 In the new main
, I create a separate awaitable Task
for each part (1, 2, & 3).在新的main
,我为每个部分(1、2 和 3)创建了一个单独的可等待Task
。 Then, I await
them in sequence.然后,我按顺序await
他们。
When t1
runs, it hits an await
after the first print.当t1
运行时,它在第一次打印后遇到await
。 This pauses part1
at that point until the awaitable completes.这会暂停part1
直到等待完成。 Program control will return to the caller ( main
) until that point, similar to how yield
works.程序控制将返回到调用者 ( main
) 直到那时,类似于yield
工作方式。
While t1
is "paused" (waiting), main
will continue on and start up t2
.当t1
被“暂停”(等待)时, main
将继续并启动t2
。 t2
does the same thing as t1
, so startup of t3
will follow shortly after. t2
与t1
做同样的事情,所以t3
启动将紧随其后。 t3
does no await
-ing, so its output occurs immediately. t3
没有await
-ing,所以它的输出会立即发生。
At this point, main
is just waiting for its child Task
s to finish up.此时, main
只是在等待其子Task
完成。 t1
was await
-ed first, so it will return first, followed shortly by t2
. t1
首先是await
-ed,因此它将首先返回,然后是t2
。 The end result is (where test.py
is the script I put this in):最终结果是(其中test.py
是我放入的脚本):
~/.../> py .\test.py
1
2
3
4
5
6
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.