python - 现在开始异步任务,稍后等待
问题描述
C# 程序员试图学习一些 Python。我正在尝试运行 CPU 密集型计算,同时让 IO 绑定异步方法在后台悄悄地消失。在 C# 中,我通常会设置可等待运行,然后启动 CPU 密集型代码,然后等待 IO 任务,然后合并结果。
这是我在 C# 中的做法
static async Task DoStuff() {
var ioBoundTask = DoIoBoundWorkAsync();
int cpuBoundResult = DoCpuIntensizeCalc();
int ioBoundResult = await ioBoundTask.ConfigureAwait(false);
Console.WriteLine($"The result is {cpuBoundResult + ioBoundResult}");
}
static async Task<int> DoIoBoundWorkAsync() {
Console.WriteLine("Make API call...");
await Task.Delay(2500).ConfigureAwait(false); // non-blocking async call
Console.WriteLine("Data back.");
return 1;
}
static int DoCpuIntensizeCalc() {
Console.WriteLine("Do smart calc...");
Thread.Sleep(2000); // blocking call. e.g. a spinning loop
Console.WriteLine("Calc finished.");
return 2;
}
这是python中的等效代码
import time
import asyncio
async def do_stuff():
ioBoundTask = do_iobound_work_async()
cpuBoundResult = do_cpu_intensive_calc()
ioBoundResult = await ioBoundTask
print(f"The result is {cpuBoundResult + ioBoundResult}")
async def do_iobound_work_async():
print("Make API call...")
await asyncio.sleep(2.5) # non-blocking async call
print("Data back.")
return 1
def do_cpu_intensive_calc():
print("Do smart calc...")
time.sleep(2) # blocking call. e.g. a spinning loop
print("Calc finished.")
return 2
await do_stuff()
重要的是,请注意 CPU 密集型任务由无法等待的阻塞睡眠表示,IO 绑定任务由可等待的非阻塞睡眠表示。
在 C# 中运行需要 2.5 秒,在 Python 中运行需要 4.5 秒。不同之处在于 C# 立即运行异步方法,而 python 仅在遇到等待时才启动该方法。下面的输出证实了这一点。我怎样才能达到预期的结果。如果可能的话,将不胜感激在 Jupyter Notebook 中工作的代码。
--- C# ---
Make API call...
Do smart calc...
Calc finished.
Data back.
The result is 3
--- Python ---
Do smart calc...
Calc finished.
Make API call...
Data back.
The result is 3
更新 1
受 knh190 的回答启发,我似乎可以使用asyncio.create_task(...)
. 这达到了预期的结果(2.5 秒):首先,异步代码设置为运行;接下来,阻塞CPU代码同步运行;第三,等待异步代码;最后合并结果。为了让异步调用真正开始运行,我必须输入一个await asyncio.sleep(0)
,这感觉就像一个可怕的 hack。我们可以在不这样做的情况下设置任务运行吗?一定会有更好的办法...
async def do_stuff():
task = asyncio.create_task(do_iobound_work_async())
await asyncio.sleep(0) # <~~~~~~~~~ This hacky line sets the task running
cpuBoundResult = do_cpu_intensive_calc()
ioBoundResult = await task
print(f"The result is {cpuBoundResult + ioBoundResult}")
解决方案
我认为您的测试几乎是不言自明的。Python 的前身是生成await
器async
(在 Python 2 中)。Python 只创建一个协程,但在您显式调用它之前不会启动它。
因此,如果您想像 C# 一样立即触发协程,则需要将await
行向前移动。
async def do_stuff():
ioBoundTask = do_iobound_work_async() # created a coroutine
ioBoundResult = await ioBoundTask # start the coroutine
cpuBoundResult = do_cpu_intensive_calc()
print(f"The result is {cpuBoundResult + ioBoundResult}")
这相当于:
def do_stuff():
# create a generator based coroutine
# cannot mix syntax of asyncio
ioBoundTask = do_iobound_work_async()
ioBoundResult = yield from ioBoundTask
# whatever
另见这篇文章:在实践中,Python 3.3 中新的“yield from”语法的主要用途是什么?
我注意到您的 C# 和 Python 并不严格等价。Python 中只有 asyncio.Task 是并发的:
async def do_cpu_intensive_calc():
print("Do smart calc...")
await asyncio.sleep(2)
print("Calc finished.")
return 2
# 2.5s
async def do_stuff():
task1 = asyncio.create_task(do_iobound_work_async())
task2 = asyncio.create_task(do_cpu_intensive_calc())
ioBoundResult = await task1
cpuBoundResult = await task2
print(f"The result is {cpuBoundResult + ioBoundResult}")
现在执行时间应该是一样的。
推荐阅读
- xml - 非空 IXMLNodeCollection.Count 返回 0
- oauth-2.0 - 如何使用节点 js 从访问令牌中获取谷歌刷新令牌和访问令牌
- scala - 有没有办法用scala中的高阶方法替换嵌套的For循环
- java - 如何修复错误创建名为“requestMappingHandlerMapping”的bean
- javascript - 如何在 nuxt.js 中导入 wavesurfer?
- mps - 在 MPS 中执行非 ui 阻塞任务并在任务完成后更改模型
- css - 如何为页面的特定部分添加背景图片?
- java - 由于 ViewPostIME,按钮不执行任何操作
- verilog - 如何计算位/字节大小?
- rest - Onion Architecture - 如果在将结构化数据(p.ex:一个对象)提供给用例后有一些数据要检查,接口应该做什么