Python asyncio 任务排序

d51*_*512 8 python queue event-loop python-asyncio

我有一个关于 pythonasyncio模块中的事件循环如何管理未完成任务的问题。考虑以下代码:

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]))
Run Code Online (Sandbox Code Playgroud)

运行这将打印:

a.0
b.0
c.0
a.1
b.1
c.1
a.2
b.2
c.2
Run Code Online (Sandbox Code Playgroud)

请注意,它总是打印出 'a' 然后是 'b' 然后是 'c'。我猜无论每个协程经过多少次迭代,它都将始终按该顺序打印。所以你永远不会看到类似的东西

b.100
c.100
a.100
Run Code Online (Sandbox Code Playgroud)

来自 node.js 背景,这告诉我这里的事件循环在内部维护一个队列,它用来决定接下来运行哪个任务。它最初放在a()队列的前面,然后是b(),然后c()因为这是传递给 的列表中任务的顺序asyncio.wait()。然后每当它遇到一个 yield 语句时,它就会将该任务放在队列的末尾。我想在一个更现实的例子中,假设你在做一个异步 http 请求,它会放a()在 http 响应回来后放回队列的末尾。

我可以得到一个阿门吗?

Udi*_*Udi 4

目前您的示例不包含任何阻塞 I/O 代码。尝试这个来模拟一些任务:

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()
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,协程是根据需要而不是按顺序来安排的。

  • 我很欣赏您的回复,但我的问题不是关于“asyncio”的适当使用,而是关于它是如何实现的。当然,对库实现细节做出假设是不安全的,我并不是想通过问这个问题来暗示其他情况。只是想看看是否有人知道它是如何工作的。 (4认同)
  • 是的,它会尽快运行回调,因此如果 I/O 需要不同的时间才能完成,它们就会开始乱序。我的观点是,如果没有发生 I/O(如我的示例),由于幕后发生的某种任务管理(可能是队列),它们总是以可预测的顺序运行。与线程之类的东西相反,线程将根据操作系统线程调度程序运行它们并且是不可预测的。 (3认同)