是否有可能使装饰器在asyncio执行器中运行阻塞函数?

时间:2019-03-07 21:13:30

标签: python python-3.x asynchronous decorator python-asyncio

我可能需要帮助来更好地说明这个问题。我正在通过python3.7和类(称为Worker())编写一个异步api接口。 Worker有一些我想使用loop.run_in_executor()运行的阻止方法。

我想构建一个装饰器,我可以在async中的所有非Worker方法之上添加,但是我一直遇到问题。

有人告诉我我需要在下面的装饰器中await wraps()

def run_method_in_executor(func, *, loop=None):
    async def wraps(*args):
        _loop = loop if loop is not None else asyncio.get_event_loop()
        return await _loop.run_in_executor(executor=None, func=func, *args)
    return wraps

返回: RuntimeWarning: coroutine 'run_method_in_executor.<locals>.wraps' was never awaited

我没有看到如何正确await wraps(),因为包含函数和修饰函数不是异步的。不确定这是由于对asyncio的误解还是对装饰器的误解。

任何帮助(或帮助澄清)将不胜感激!

2 个答案:

答案 0 :(得分:2)

Not_a_Golfer在评论中回答了我的问题。

将内部wraps()函数从协程更改为生成器可以解决此问题:

def run_method_in_executor(func, *, loop=None):
    def wraps(*args):
        _loop = loop if loop is not None else asyncio.get_event_loop()
        yield _loop.run_in_executor(executor=None, func=func, *args)
    return wraps

编辑: 这对于IO确实非常有用,但是我还没有弄清楚如何await产生的执行程序函数,这意味着如果我依靠修饰符,它将创建竞争条件函数来更新其他异步函数使用的某些值。

答案 1 :(得分:1)

这是Python 3.6+的完整示例,该示例不使用3.8弃用的接口。返回loop.run_in_executor的值可将包装的函数有效地转换为在线程中执行的 awaitable ,因此您可以await完成它。

#!/usr/bin/env python3

import asyncio
import functools
import time


def run_in_executor(_func):
    @functools.wraps(_func)
    def wrapped(*args, **kwargs):
        loop = asyncio.get_event_loop()
        func = functools.partial(_func, *args, **kwargs)
        return loop.run_in_executor(executor=None, func=func)
    return wrapped


@run_in_executor
def say(text=None):
    """Block, then print."""
    time.sleep(1.0)
    print(f'say {text} at {time.monotonic():.3f}')


async def main():
    print(f'beginning at {time.monotonic():.3f}')
    await asyncio.gather(say('asdf'), say('hjkl'))
    await say(text='foo')
    await say(text='bar')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
beginning at 3461039.617
say asdf at 3461040.618
say hjkl at 3461040.618
say foo at 3461041.620
say bar at 3461042.621