如何在异步协程中包装同步函数?

时间:2017-04-05 20:40:04

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

我使用aiohttp构建一个API服务器,将TCP请求发送到单独的服务器。发送TCP请求的模块是同步的,并且是出于我的目的的黑盒子。所以我的问题是这些请求阻止了整个API。我需要一种方法来将模块请求包装在异步协程中,该协同程序不会阻止其余的API。

所以,仅仅使用sleep作为一个简单的例子,有没有办法以某种方式将耗时的同步代码包装在非阻塞协程中,如下所示:

async def sleep_async(delay):
    # After calling sleep, loop should be released until sleep is done
    yield sleep(delay)
    return 'I slept asynchronously'

5 个答案:

答案 0 :(得分:25)

最终我在this thread找到了答案。我正在寻找的方法是run_in_executor。这允许同步函数异步运行而不会阻塞事件循环。

在我上面发布的sleep示例中,它可能如下所示:

import asyncio
from time import sleep
from concurrent.futures import ProcessPoolExecutor

async def sleep_async(loop, delay):
    # Can set executor to None if a default has been set for loop
    await loop.run_in_executor(ProcessPoolExecutor(), sleep, delay)
    return 'I slept asynchronously'

另见以下答案 - > How do we call a normal function where a coroutine is expected?

答案 1 :(得分:10)

您可以使用装饰器将同步版本包装为异步版本。

import time
from functools import wraps, partial


def wrap(func):
    @wraps(func)
    async def run(*args, loop=None, executor=None, **kwargs):
        if loop is None:
            loop = asyncio.get_event_loop()
        pfunc = partial(func, *args, **kwargs)
        return await loop.run_in_executor(executor, pfunc)
    return run

@wrap
def sleep_async(delay):
    time.sleep(delay)
    return 'I slept asynchronously'

或使用aioify lib

% pip install aioify

然后

@aioify
def sleep_async(delay):
    pass

答案 2 :(得分:1)

不确定是否为时已晚,但是您也可以使用装饰器在线程中执行功能。虽然如此,但请注意,与异步合作是合作社阻止一样,它仍将是非合作社阻止。

def wrap(func):
    from concurrent.futures import ThreadPoolExecutor
    pool=ThreadPoolExecutor()
    @wraps(func)
    async def run(*args, loop=None, executor=None, **kwargs):
        if loop is None:
            loop = asyncio.get_event_loop()
        future=pool.submit(func, *args, **kwargs)
        return asyncio.wrap_future(future)
    return run

希望有帮助!

答案 3 :(得分:1)

也许有人需要我解决这个问题。我编写了自己的库来解决此问题,该库使您可以使用装饰器使任何函数异步进行。

要安装该库,请运行以下命令:

$ pip install awaits

要使任何函数异步,只需向其添加@awaitable装饰器,如下所示:

import time
import asyncio
from awaits.awaitable import awaitable

@awaitable
def sum(a, b):
  # heavy load simulation
  time.sleep(10)
  return a + b

现在,您可以确保您的函数确实是异步协程:

print(asyncio.run(sum(2, 2)))

“内幕”将在线程池中执行您的函数。每次调用函数时都不会重新创建此线程池。线程池创建一次,并通过队列接受新任务。这将使您的程序比使用其他解决方案更快地运行,因为创建更多线程是额外的开销。

答案 4 :(得分:1)

装饰器在这种情况下很有用,并在另一个线程中运行您的阻塞函数。

import asyncio
from concurrent.futures import ThreadPoolExecutor
from functools import wraps, partial
from typing import Union

class to_async:

    def __init__(self, *, executor: Optional[ThreadPoolExecutor]=None):
       
        self.executor =  executor
    
    def __call__(self, blocking):
        @wraps(blocking)
        async def wrapper(*args, **kwargs):

            loop = asyncio.get_event_loop()
            if not self.executor:
                self.executor = ThreadPoolExecutor()

            func = partial(blocking, *args, **kwargs)
        
            return await loop.run_in_executor(self.executor,func)

        return wrapper

@to_async(executor=None)
def sync(*args, **kwargs):
    print(args, kwargs)
   
asyncio.run(sync("hello", "world", result=True))