asyncio.to_thread()方法与ThreadPoolExecutor不同吗?
我看到 @python 3.9+ 添加了 asyncio.to_thread() 方法,它的描述说它在单独的线程上运行阻塞代码以立即运行。见下面的例子:
def blocking_io():
print(f"start blocking_io at {time.strftime('%X')}")
# Note that time.sleep() can be replaced with any blocking
# IO-bound operation, such as file operations.
time.sleep(1)
print(f"blocking_io complete at {time.strftime('%X')}")
async def main():
print(f"started main at {time.strftime('%X')}")
await asyncio.gather(
asyncio.to_thread(blocking_io),
asyncio.sleep(1))
print(f"finished main at {time.strftime('%X')}")
asyncio.run(main())
# Expected output:
#
# started main at 19:50:53
# start blocking_io at 19:50:53
# blocking_io complete at 19:50:54
# finished main at 19:50:54
通过解释,它似乎使用线程机制而不是上下文切换或协程。这是否意味着它实际上并不是异步的?它与concurrent.futures.ThreadPoolExecutor中的传统多线程相同吗?那么这样使用线程有什么好处呢?
回答
源代码的to_thread非常简单。它归结为等待run_in_executor带有默认执行程序(执行程序参数是None),即ThreadPoolExecutor。
事实上,是的,这是传统的多线程,旨在在单独线程上运行的 ode 不是异步的,但to_thread允许您await异步获取其结果。
另请注意,该函数在当前任务的上下文中运行,因此其上下文变量值将在func.
async def to_thread(func, /, *args, **kwargs):
"""Asynchronously run function *func* in a separate thread.
Any *args and **kwargs supplied for this function are directly passed
to *func*. Also, the current :class:`contextvars.Context` is propogated,
allowing context variables from the main thread to be accessed in the
separate thread.
Return a coroutine that can be awaited to get the eventual result of *func*.
"""
loop = events.get_running_loop()
ctx = contextvars.copy_context()
func_call = functools.partial(ctx.run, func, *args, **kwargs)
return await loop.run_in_executor(None, func_call)
THE END
二维码