如何创建一组可同时同步和异步使用的函数?

2024-03-29 15:54:03 发布

您现在位置:Python中文网/ 问答频道 /正文

假设我有这样一组函数:

def func1():
    func2()

def func2():
    time.sleep(1)  # simulate I/O operation
    print('done')

我希望这些可以同步使用:

# this would take two seconds to complete
func1()
func1()

以及异步,例如:

# this would take 1 second to complete
future = asyncio.gather(func1.run_async(), func1.run_async())
loop = asyncio.get_event_loop()
loop.run_until_complete(future)

当然,问题是func1必须以某种方式将它运行的“上下文”(同步与异步)传播到func2。你知道吗

我希望避免编写每个函数的异步变体,因为这样会导致大量重复代码:

def func1():
    func2()

def func2():
    time.sleep(1)  # simulate I/O operation
    print('done')

# duplicate code below...
async def func1_async():
    await func2_async()

async def func2_async():
    await asyncio.sleep(1)  # simulate I/O operation
    print('done')

有没有什么方法可以做到这一点而不必实现所有函数的异步拷贝?你知道吗


Tags: 函数runloopasyncioasynctimedefsleep
2条回答

这是我的“没有答案”,我知道Stack Overflow喜欢。。。你知道吗

Is there any way to do this without having to implement an asynchronous copy of all my functions?

我认为没有。制作一个“一揽子翻译程序”将函数转换为本地协同程序似乎几乎是不可能的。这是因为使同步函数异步不仅仅是在它前面抛出一个async关键字和几个await语句。你知道吗

你的def func2(): time.sleep(1)说明了这一点。同步函数将进行阻塞调用,例如time.sleep();异步(本机协程)将等待非阻塞协程。正如您所指出的,使这个函数异步化不仅需要使用async def func(),而且需要等待asyncio.sleep()。现在让我们假设您调用的不是time.sleep(),而是更复杂的阻塞函数。您构建了某种花哨的decorator,将名为run_asyncfunction attribute放在修饰函数上,这是一个可调用的函数。但是,如果定义了func2()中的阻塞调用,那么修饰符如何知道如何将这些阻塞调用“转换”为它们的协同程序等价物呢?我想不出有什么魔法能聪明到将同步函数中的所有调用都转换成它们的await可对应的调用。你知道吗

在您的评论中,您提到这是针对HTTP请求的。对于一个真实的例子,requestsaiohttp包之间的调用签名和api的差异。在aiohttp中,.text()是实例method;在requests中,.text是实例property。你怎么能建立一个足够聪明的东西来知道这样的区别呢?你知道吗

我并不想让人沮丧,但我认为使用线程将更现实。你知道吗

所以我找到了一个方法来实现这一点,但是由于这是我第一次用async做任何事情,我不能保证这没有任何错误或者这不是一个糟糕的主意。你知道吗

这个概念实际上非常简单:在必要的地方使用async defawait像普通异步函数一样定义函数,然后在它们周围添加一个包装器,如果没有运行事件循环,它会自动等待函数。概念证明:

import asyncio
import functools
import time


class Hybrid:
    def __init__(self, func):
        self._func = func

        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        coro = self._func(*args, **kwargs)

        loop = asyncio.get_event_loop()

        if loop.is_running():
            # if the loop is running, we must've been called from a
            # coroutine - so we'll return a future
            return loop.create_task(coro)
        else:
            # if the loop isn't running, we must've been called synchronously,
            # so we'll start the loop and let it execute the coroutine
            return loop.run_until_complete(coro)

    def run_async(self, *args, **kwargs):
        return self._func(*args, **kwargs)


@Hybrid
async def func1():
    await func2()

@Hybrid
async def func2():
    await asyncio.sleep(0.1)


def twice_sync():
    func1()
    func1()

def twice_async():
    future = asyncio.gather(func1.run_async(), func1.run_async())
    loop = asyncio.get_event_loop()
    loop.run_until_complete(future)


for func in [twice_sync, twice_async]:
    start = time.time()
    func()
    end = time.time()
    print('{:>11}: {} sec'.format(func.__name__, end-start))

# output:
#  twice_sync: 0.20142340660095215 sec
# twice_async: 0.10088586807250977 sec

然而,这种方法确实有其局限性。如果同步函数调用混合函数,则从异步函数调用同步函数将更改其行为:

@hybrid
async def hybrid_function():
    return "Success!"

def sync_function():
    print('hybrid returned:', hybrid_function())

async def async_function():
    sync_function()

sync_function()  # this prints "Success!" as expected

loop = asyncio.get_event_loop()
loop.run_until_complete(async_function())  # but this prints a coroutine

小心点!你知道吗

相关问题 更多 >