如何像C#中那样同步运行Python协程,直到第一次等待



我正在使用一些异步Python代码,遇到了协程排序问题。我对C#有一些经验,所以我将以它为例。

在C#中,我可以写:

using System;
using System.Threading.Tasks;
public class Program
{
async static Task<int> foo(int n, int delay)
{
Console.WriteLine("Foo start {0}",n);
await Task.Delay(delay);
Console.WriteLine("Foo end {0}",n);
return 0;
}
public static void Main()
{
Task<int> t1 = foo(1,300);
Task<int> t2 = foo(2,200);

Task.WaitAll(new []{t2,t1});// Reverse on purpose
}
}

结果如下,注意协同程序的开始与调用顺序匹配,而不是等待顺序。这是有保证的,因为协程函数是如何划分成生成器的。即它们总是同步运行,直到第一个CCD_ 1。

Foo start 1
Foo start 2
Foo end 2
Foo end 1

但当我在Python中做同样的事情时,我会得到不同的顺序。

import asyncio
async def foo(n,delay):
print(f"Foo start {n}")
await asyncio.sleep(delay/1000)
print(f"Foo end {n}")
return 0

async def main():
t1 = foo(1,300)
t2 = foo(2,200)

await asyncio.gather(t2,t1) # Reverse on purpose

asyncio.run(main())
Foo start 2
Foo start 1
Foo end 2
Foo end 1

我也知道这是为什么——因为foo(1,300)返回一个生成器,它在等待/调度协程之前不会启动。

我的问题是如何在Python中实现类似C#的行为,这意味着协程在调用时开始,直到第一个await

我试过

t1 = asyncio.create_task(foo(1,300))
t2 = asyncio.create_task(foo(2,200))

哪个解决了这个问题,但调度顺序真的有保证吗?

顾名思义,asyncio.create_task()创建了一个task.Task实例。任务在具有的task.Task的构造函数中进行调度
self._loop.call_soon(self.__step, context=self._context)

来自BaseEventLoop.call_soon:的文档字符串

安排尽快调用回调。这作为FIFO队列操作:回调在它们的注册顺序。每次回调将只打了一次电话。

所以,只要你使用的事件循环不覆盖这种行为,我想说你可以按照你想要的方式使用create_task。据我所知,asyncio中没有一个事件循环能覆盖。

你试过这个吗:

t1 = asyncio.create_task(foo(1,300))
await asyncio.sleep(0.0)
t2 = asyncio.create_task(foo(2,200))
await asyncio.sleep(0.0)

这应该总是有效的,因为它在每次任务创建后都会屈服于事件循环。每个任务都应该启动并运行到第一个等待,然后再创建下一个任务。我说";应该";因为我自己在实践中并没有真正做到这一点。但我强烈希望它在任何情况下都能发挥作用。

如果你有很多任务,你可以用下面这样的小功能清理代码:

async def start_sequential(*coros):
for c in coros:
asyncio.create_task(c)
await asyncio.sleep(0.0)
c1 = foo(1, 300)
c2 = foo(2, 200)
await start_sequential(c1, c2)

最新更新