Big*_* AL 6 python async-await jupyter-notebook
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;
}
Run Code Online (Sandbox Code Playgroud)
这是python中的等效代码
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;
}
Run Code Online (Sandbox Code Playgroud)
Importantly, please note that the CPU intensive task is represented by a blocking sleep that cannot be awaited and the IO bound task is represented by a non-blocking sleep that is awaitable.
This takes 2.5 seconds to run in C# and 4.5 seconds in Python. The difference is that C# runs the asynchronous method straight away whereas python only starts the method when it hits the await. Output below confirms this. How can I achieve the desired result. Code that would work in Jupyter Notebook would be appreciated if at all possible.
--- C# ---
Make API call...
Do smart calc...
Calc finished.
Data back.
The result is 3
Run Code Online (Sandbox Code Playgroud)
--- Python ---
Do smart calc...
Calc finished.
Make API call...
Data back.
The result is 3
Run Code Online (Sandbox Code Playgroud)
受knh190答案的启发,似乎我可以使用来获得大部分信息asyncio.create_task(...)。这样可以达到预期的结果(2.5秒):首先,将异步代码设置为运行;接下来,阻塞的CPU代码将同步运行;第三,等待异步代码;最后将结果合并。为了使异步调用真正开始运行,我必须插入一个await asyncio.sleep(0),这简直是骇人听闻的破解。我们可以不执行此操作而设置任务运行吗?肯定有更好的办法...
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()
Run Code Online (Sandbox Code Playgroud)
因此,通过更多的研究,这似乎是可能的,但不像 C# 那样容易。的代码do_stuff()变为:
async def do_stuff():
task = asyncio.create_task(do_iobound_work_async()) # add task to event loop
await asyncio.sleep(0) # return control to loop so task can start
cpuBoundResult = do_cpu_intensive_calc() # run blocking code synchronously
ioBoundResult = await task # at last, we can await our async code
print(f"The result is {cpuBoundResult + ioBoundResult}")
Run Code Online (Sandbox Code Playgroud)
与 C# 相比,两个区别是:
asyncio.create_task(...)将任务添加到正在运行的事件循环中所需await asyncio.sleep(0)暂时将控制权返回给事件循环,以便它可以启动任务。完整的代码示例现在是:
import time
import asyncio
async def do_stuff():
task = asyncio.create_task(do_iobound_work_async()) # add task to event loop
await asyncio.sleep(0) # return control to loop so task can start
cpuBoundResult = do_cpu_intensive_calc() # run blocking code synchronously
ioBoundResult = await task # at last, we can await our async code
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. Hence the use of asyncio
print("Data back.")
return 1
def do_cpu_intensive_calc():
print("Do smart calc...")
time.sleep(2) # long blocking code that cannot be awaited. e.g. a spinning loop
print("Calc finished.")
return 2
await do_stuff()
Run Code Online (Sandbox Code Playgroud)
我不太喜欢必须记住添加额外内容await asyncio.sleep(0)才能开始任务。拥有一个像这样自动启动任务运行的可等待函数可能会更简洁,begin_task(...)以便可以在稍后阶段等待它。例如,如下所示:
async def begin_task(coro):
"""Awaitable function that adds a coroutine to the event loop and sets it running."""
task = asyncio.create_task(coro)
await asyncio.sleep(0)
return task
async def do_stuff():
io_task = await begin_task(do_iobound_work_async())
cpuBoundResult = do_cpu_intensive_calc()
ioBoundResult = await io_task
print(f"The result is {cpuBoundResult + ioBoundResult}")
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
250 次 |
| 最近记录: |