简体   繁体   English

Python asyncio 任务排序

[英]Python asyncio task ordering

I have a question about how the event loop in python's asyncio module manages outstanding tasks.我有一个关于 python 的asyncio模块中的事件循环如何管理未完成任务的问题。 Consider the following code:考虑以下代码:

import asyncio

@asyncio.coroutine
def a():
   for i in range(0, 3):
      print('a.' + str(i))
      yield


@asyncio.coroutine
def b():
   for i in range(0, 3):
      print('b.' + str(i))
      yield


@asyncio.coroutine
def c():
   for i in range(0, 3):
      print('c.' + str(i))
      yield


tasks = [
   asyncio.Task(a()),
   asyncio.Task(b()),
   asyncio.Task(c()),
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([t1, t2, t3]))

Running this will print:运行它将打印:

a.0
b.0
c.0
a.1
b.1
c.1
a.2
b.2
c.2

Notice that it always prints out 'a' then 'b' then 'c'.请注意,它总是打印出 'a' 然后 'b' 然后 'c'。 I'm guessing that no matter how many iterations each coroutine goes through it will always print in that order.我猜无论每个协程经过多少次迭代,它总是会按该顺序打印。 So you'd never see something like所以你永远不会看到类似的东西

b.100
c.100
a.100

Coming from a node.js background, this tells me that the event loop here is maintaining a queue internally that it uses to decide which task to run next.来自 node.js 背景,这告诉我这里的事件循环在内部维护一个队列,它用来决定接下来运行哪个任务。 It initially puts a() at the front of the queue, then b() , then c() since that's the order of the tasks in the list passed to asyncio.wait() .它最初将a()放在队列的前面,然后是b() ,然后是c() ,因为这是传递给asyncio.wait()的列表中任务的顺序。 Then whenever it hits a yield statement it puts that task at the end of the queue.然后,每当它遇到一个 yield 语句时,它就会将该任务放在队列的末尾。 I guess in a more realistic example, say if you were doing an async http request, it would put a() back on the end of the queue after the http response came back.我想在一个更现实的例子中,假设您正在执行异步 http 请求,它会在 http 响应返回后将a()放回队列的末尾。

Can I get an amen on this?我可以得到一个阿门吗?

Currently your example doesn't include any blocking I/O code.目前,您的示例不包含任何阻塞 I/O 代码。 Try this to simulate some tasks:试试这个来模拟一些任务:

import asyncio


@asyncio.coroutine
def coro(tag, delay):
    for i in range(1, 8):
        print(tag, i)
        yield from asyncio.sleep(delay)


loop = asyncio.get_event_loop()

print("---- await 0 seconds :-) --- ")
tasks = [
    asyncio.Task(coro("A", 0)),
    asyncio.Task(coro("B", 0)),
    asyncio.Task(coro("C", 0)),
]

loop.run_until_complete(asyncio.wait(tasks))

print("---- simulate some blocking I/O --- ")
tasks = [
    asyncio.Task(coro("A", 0.1)),
    asyncio.Task(coro("B", 0.3)),
    asyncio.Task(coro("C", 0.5)),
]

loop.run_until_complete(asyncio.wait(tasks))

loop.close()

As you can see, coroutines are scheduled as needed, and not in order.如您所见,协程是按需要安排的,而不是按顺序安排的。

DISCLAIMER For at least v3.9 with the default implementation this appears to be true.免责声明至少对于具有默认实现的 v3.9,这似乎是正确的。 However, the inner workings of the event loop are not public interface and thus may be changed with new versions.然而,事件循环的内部工作不是公共接口,因此可能会随着新版本的变化而改变。 Additionally, asyncio allows for BaseEventLoop implementation to be substituted, which may change its behavior.此外,asyncio 允许替换BaseEventLoop实现,这可能会改变其行为。

When a Task object is created, it calls loop.call_soon to register its _step method as a callback.创建Task对象时,它会调用loop.call_soon将其_step方法注册为回调。 The _step method actually does the work of calling your coroutine with calls to send() and processing the results. _step方法实际上是通过调用send()来调用协程并处理结果。

In BaseEventLoop , loop.call_soon places the _step callback at the end of a _ready list of callbacks.BaseEventLoop中, loop.call_soon_step回调放在_ready回调列表的末尾。 Each run of the event loop, iterates the list of _ready callbacks in a FIFO order and calls them.事件循环的每次运行,都会以 FIFO 顺序迭代_ready回调列表并调用它们。 Thus, for the initial run of tasks, they are executed in the order they are created.因此,对于任务的初始运行,它们按照创建的顺序执行。

When the task awaits or yield sa future, it really depends on the nature of that future when the task's _wakeup method get put into the queue.当任务awaitsyield一个未来时,它实际上取决于任务的_wakeup方法被放入队列时该未来的性质。

Also, note that other callbacks can be registered in between creation of tasks.另外,请注意,可以在创建任务之间注册其他回调。 While it is true that if TaskA is created before TaskB , the initial run of TaskA will happen before TaskB , there could still be other callbacks that get run in between.虽然如果TaskA是在 TaskB 之前创建的,那么TaskB的初始运行将在TaskA之前TaskB ,但仍然可能有其他回调在其间运行。

Last, the above behavior is also for the default Task class that comes with asyncio .最后,上述行为也适用于asyncio附带的默认Task类。 Its possible however to specify a custom task factory and use an alternative task implementation which could also change this behavior.但是,可以指定自定义任务工厂并使用也可以更改此行为的替代任务实现。

(This is a follow up to D-Rock's answer, was too long to be a comment.) (这是对 D-Rock 回答的跟进,太长了,无法发表评论。)

The execution order of callbacks is guaranteed in the asyncio documentation in a few places.回调的执行顺序在一些地方的 asyncio 文档中得到保证。

The loop.call_soon() docs guarantee the execution order : loop.call_soon()文档保证执行顺序

Callbacks are called in the order in which they are registered.回调按照它们注册的顺序被调用。 Each callback will be called exactly once.每个回调将被调用一次。

The Future.add_done_callback() docs specify that callbacks are scheduled via loop.call_soon() , and thus have this guaranteed FIFO order. Future.add_done_callback()文档指定回调是通过loop.call_soon()安排的,因此有这个保证的 FIFO 顺序。

And asyncio.Task described as a subclass of asyncio.Future , and so has the same behaviour for add_done_callback() .并且asyncio.Task被描述为asyncio.Future的子类,因此对于add_done_callback()具有相同的行为。

So I think it's pretty safe to rely on FIFO ordering of asyncio callbacks, at least when using vanilla asyncio.所以我认为依赖异步回调的 FIFO 排序是非常安全的,至少在使用 vanilla asyncio 时是这样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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