Итак, я нашел способ достичь этого, но, поскольку это буквально первый раз, когда я что-то сделал с async
, я не могу гарантировать, что в нем нет ошибок или что это не ужасная идея.
Концепция на самом деле довольно проста: определите ваши функции как обычные асинхронные функции, используя async def
и await
, где это необходимо, а затем добавьте обертку вокруг них, которая автоматически ожидает функцию , если без цикла обработки событий бежит. Подтверждение концепции:
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
Позаботьтесь об этом!