我有一个服务器应用程序,当客户端请求时,我会安排一些工作,比如
def work():
time.sleep(5)
fut = asyncio.get_event_loop().run_in_executor(None, work)
Iawait fut
。我的用例要求run_in_executor
立即提交work
函数,并且这在我的环境(Ubuntu 16.04,Python 3.7.1)中的行为与预期一致
由于我的应用程序依赖于这种行为,我想验证它是否可能发生更改,所以我检查了几个资源:
- 文档似乎有点模糊awaitable似乎可以应用于方法或返回值,尽管文本正文确实明确表示它返回
asyncio.Future
- 指定asyncio的PEP3156——这里它没有说
run_in_executor
是一个协程 - 在一些问题中,
run_in_executor
是一个返回可重写的函数还是一个协程本身似乎被认为是一个实现细节。参见25675和32327 AbstractEventLoop.run_in_executor
被指定为协程,但BaseEventLoop.run_in_executor
中的实现是一个普通函数
1和2似乎主要表明当前行为是正确的,但3和4令人担忧。这似乎是接口的一个非常重要的部分,因为如果函数本身是一个协程,那么它在等待之前不会开始执行(因此不会安排工作)。
依赖当前的行为安全吗?如果是这样,将AbstractEventLoop.run_in_executor
的接口改为普通函数而不是协程是否合理?
我的用例要求
run_in_executor
立即提交工作函数,并且这在我的环境中的行为与预期一致
当前行为不受文档的保证,文档仅指定函数为要调用的func
安排,并返回一个不可用的。如果它是用协程实现的,那么在事件循环运行之前,它不会提交。
然而,这种行为从一开始就存在,在未来极不可能改变。延迟提交虽然在技术上是文档允许的,但会破坏许多现实世界中的异步应用程序,并构成严重的向后不兼容的更改。
如果您想确保任务的启动不依赖于未记录的行为,那么您可以创建自己的等效于run_in_executor
的函数。它实际上可以归结为executor.submit
和asyncio.wrap_future
的结合。没有虚饰,它可以简单到:
def my_run_in_executor(executor, f, *args):
return asyncio.wrap_future(executor.submit(f, *args))
由于executor.submit
是在函数中直接调用的,因此此版本可确保在不等待事件循环运行的情况下启动辅助函数。
PEP 3156明确指出run_in_executor
"等同于wrap_future(executor.submit(callback, *args))
",从而提供了所需的保证——但PEP不是官方文件,最终的实现和规范往往与最初的PEP不同。
如果坚持使用run_in_executor
的文档化接口,也可以使用显式同步来强制协同程序等待工作程序启动:
async def run_now(f, *args):
loop = asyncio.get_event_loop()
started = asyncio.Event()
def wrapped_f():
loop.call_soon_threadsafe(started.set)
return f(*args)
fut = loop.run_in_executor(None, wrapped_f)
await started.wait()
return fut
fut = await run_now(work)
# here the worker has started, but not (necessarily) finished
result = await fut
# here the worker has finished and we have its return value
这种方法引入了不必要的实现和接口复杂性,尤其是需要使用await
来获得未来,这与异步的正常工作方式背道而驰。run_now
只是为了完整性而包含在内,我不建议在生产中使用它。