我正在尝试了解新的 asyncio 协程(在 Python 3.5 中引入)。
1997 年,我在大学参加了一门课程,大致涵盖了 Andrew Tanenbaum 所著的《现代操作系统》一书的内容。 不知何故,Python3 中的
await
让我想起了
协作多任务处理。 来自维基百科:
协作多任务处理,也称为非抢占式多任务处理,是一种计算机多任务处理方式,其中操作系统从不启动从正在运行的进程到另一个进程的上下文切换。相反,进程会定期或在空闲时自愿放弃控制,以便使多个应用程序能够同时运行。这种类型的多任务处理称为“协作”,因为所有程序都必须协作才能使整个调度方案发挥作用。
如果将 Python 解释器视为操作系统,那么术语“协作多任务处理”是否适用于
await
?
但也许我错过了一些东西。2023 年更新
用一个小程序来证明一下怎么样?首先让我们以合作
asyncio.sleep
的方式睡眠一秒钟,然后让我们以阻塞
time.sleep
的方式睡眠一秒钟。让我们打印一个线程 id、在协程中花费的时间以及任务的 id。import threading
import asyncio
import time
async def async_function(i):
started = time.time()
print("Id:", i, "ThreadId:", threading.get_ident())
await asyncio.sleep(1)
time.sleep(1)
print("Id:", i, "ThreadId:", threading.get_ident(), "Time:", time.time() - started)
async def async_main():
await asyncio.gather(
async_function(1),
async_function(2),
async_function(3)
)
loop = asyncio.get_event_loop()
loop.run_until_complete(async_main())
现在让我们尝试看看:
Id: 3 ThreadId: 140027884312320
Id: 2 ThreadId: 140027884312320
Id: 1 ThreadId: 140027884312320
Id: 3 ThreadId: 140027884312320 Time: 2.002575397491455
Id: 2 ThreadId: 140027884312320 Time: 3.0038201808929443
Id: 1 ThreadId: 140027884312320 Time: 4.00504469871521
正如预期的那样。仅在一个线程中执行。
asyncio.sleep(1)
是非阻塞的,因此并发处理所有这些需要 1 秒。
time.sleep(1)
正在阻塞(它不合作),因此它会阻塞其余部分。 Id 1
等待 id 2
完成,而 id 2
等待 id 3
完成。C# 也有 async/await,它也有协作多任务吗?
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class MainClass {
private static async Task AsyncMethod(int id) {
var started = DateTime.Now;
Console.WriteLine("Id: {0} ThreadId: {1}", id, Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1000);
Thread.Sleep(1000);
Console.WriteLine("Id: {0} ThreadId: {1} Time: {2}", id, Thread.CurrentThread.ManagedThreadId, DateTime.Now - started);
}
private static async Task MainAsync()
{
await Task.WhenAll(AsyncMethod(1), AsyncMethod(2), AsyncMethod(3));
}
public static void Main (string[] args) {
MainAsync().Wait();
}
}
}
运行它并...
Id: 1 ThreadId: 1
Id: 2 ThreadId: 1
Id: 3 ThreadId: 1
Id: 2 ThreadId: 7 Time: 00:00:02.0147000
Id: 3 ThreadId: 8 Time: 00:00:02.0144560
Id: 1 ThreadId: 6 Time: 00:00:02.0878160
该死。等待之后线程不同。每个协程只需要 2 秒!怎么了?
没有什么问题。
与 Python 不同,C# 中的 async/await 结合了协作多任务和多线程。 Task.Delay(1000)
确实是非阻塞的,但当协程恢复时,它可以像示例中那样在完全不同的线程中恢复。由于协程在三个不同的线程中继续运行,因此
Thread.Sleep(1000)
并行地阻止了它们。请注意,C# 中还有更多内容可以影响此行为(例如 SynchronizationContext),但这是一个不同的主题。