简体   繁体   English

通过简单示例了解 AsyncIO 和线程顺序

[英]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:我理解它的方式线程应该是这样的:

  1. Starts main() function启动 main() 函数
  2. Starts part1() function启动 part1() 函数
  3. Prints 1打印 1
  4. While it's waiting 2 seconds, it should continue on the line p2 = await part2()当它等待 2 秒时,它应该在行 p2 = await part2() 上继续
  5. Starts part2() function启动 part2() 函数
  6. Prints 2打印 2
  7. While it's waiting for 2 seconds, it should continue on the line of main() "print('3')", then "print('4')"当它等待 2 秒时,它应该继续在 main() "print('3')" 行,然后是 "print('4')"
  8. part1() function ends its sleep, so it prints 5 part1() 函数结束它的睡眠,所以它打印 5
  9. part2() function ends its sleep, so it prints 6 part2() 函数结束它的睡眠,所以它打印 6

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!谢谢!

  1. 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() via asyncio.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. t2t1做同样的事情,所以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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM