想象一下,我有一组这样的功能:
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')
有没有办法实现我所有功能的异步副本?
答案 0 :(得分:3)
这是我的“不回答”,我知道Stack Overflow喜欢...
有没有办法实现我所有功能的异步副本?
我不认为有。制作“空白翻译器”以将功能转换为本地协程似乎是不可能的。这是因为使同步函数异步,不仅仅在其前面加上一个async
关键字和其中的几个await
语句。请记住,您所做的任何事情await
必须为awaitable。
您的def func2(): time.sleep(1)
说明了这一点。同步函数将进行阻塞调用,例如time.sleep()
;异步(本机协程)将等待非阻塞协程。如您所指出的,使此函数异步不仅需要使用async def func()
,而且还需要等待asyncio.sleep()
。现在,让我们而不是time.sleep()
来调用一个更复杂的阻塞函数。您构建了某种精美的装饰器,该装饰器将名为run_async
的{{3}}拍打到装饰函数上。但是,即使修饰符已经定义,装饰者如何知道如何将func2()
中的阻塞调用“转换”为它们的协程等效项?我想不出任何足以将同步功能中的所有调用转换为await
对应对象的魔术。
在您的评论中,您提到这是针对HTTP请求的。对于一个实际示例,requests
和aiohttp
包之间的呼叫签名和API的差异。在aiohttp
中,.text()
是实例function attribute;在requests
中,.text
是method。您如何构建足够聪明的东西来了解诸如此类的差异?
我并不是要劝阻-但我认为使用线程会更现实。
答案 1 :(得分:0)
因此,我找到了一种方法来实现这一目标,但是由于这实际上是我第一次使用async
做任何事情,所以我不能保证它没有任何错误,也不是很糟糕。想法。
这个概念实际上非常简单:在必要时使用async def
和await
定义函数,例如普通的异步函数,然后在它们周围添加一个包装器,以自动等待功能 if 没有事件循环正在运行。概念证明:
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
请注意这一点!