версия синхронизации имеет большинство логических частей, и асинхронная версия должна асинхронно вызывать версию синхронизации
Возможно, но это много работы, так как вы эффективноборьба с несоответствием цвета функции .Ваша логика должна быть написана асинхронно, с некоторыми хаки, чтобы позволить ей работать в режиме синхронизации.
Например, логический метод будет выглядеть так:
# common code that doesn't assume it's either sync or async
class FooRaw:
async def connect(self):
self.connect_info = await self._to_async(self._requester.connect(ENDPOINT))
async def hello_logic(self):
await self._to_async(self.connect())
self.sock.write('hello %s %s\n' % (USERNAME, PASSWORD))
resp = await self._to_async(self.sock.readline())
assert resp.startswith('OK')
return resp
При работе в asyncio такие методы, как connect
и readline
являются сопрограммами, поэтому следует ожидать их возвращаемого значения.С другой стороны, в коде блокировки self.connect
и sock.readline
есть функции синхронизации, которые возвращают конкретные значения.Но await
является синтаксической конструкцией, которая присутствует или отсутствует, вы не можете отключить ее во время выполнения без дублирования кода.
Чтобы разрешить одному и тому же коду работать в режимах синхронизации и асинхронности, FooRaw.hello_logic
всегда ожидает, оставляя для метода _to_async
возможность обернуть результат в ожидаемый при выполнении вне asyncio.В асинхронных классах _asincify
ожидает своего аргумента и возвращает результат, в основном это неоперация.В классах синхронизации он возвращает полученный объект, не ожидая его, но все же определяется как async def
, поэтому его можно ожидать.В этом случае FooRaw.hello_logic
по-прежнему является сопрограммой, но она никогда не приостанавливается (потому что «сопрограммы», которые она ожидает, - это все случаи _to_async
, который не приостанавливается вне asyncio.)
С этим на местеасинхронная реализация hello_logic
не должна ничего делать, кроме выбора правильного requester
и предоставления правильного _to_async
;его connect
и hello_logic
, унаследованные от FooRaw
, выполняют правильные действия автоматически:
class FooAsync(FooRaw):
def __init__(self):
self._requester = AsyncRequester()
@staticmethod
async def _to_async(x):
# we're running async, await X and return the result
result = await x
return result
В версии синхронизации, помимо реализации _to_async
, потребуется обернуть логические методы для «запуска»"сопрограмма:
class FooSync(FooRaw):
def __init__(self):
self._requester = SyncRequester()
@staticmethod
async def _to_async(x):
# we're running sync, X is the result we want
return x
# the following can be easily automated by a decorator
def connect(self):
return _run_sync(super().connect())
def hello_logic(self):
return _run_sync(super().hello_logic())
Обратите внимание, что сопрограмму можно запускать вне цикла событий только потому, что FooSync.hello_logic
является сопрограммой только по имени;базовый запросчик использует блокирующие вызовы, поэтому FooRaw.connect
и другие никогда не приостанавливаются, они завершают свое выполнение за один запуск.(Это похоже на генератор, который выполняет некоторую работу, ничего не получая.) Это свойство делает хелпер _run_sync
простым:
def _run_sync(coro):
try:
# start running the coroutine
coro.send(None)
except StopIteration as e:
# the coroutine has finished; return the result
# stored in the `StopIteration` exception
return e.value
else:
raise AssertionError("coroutine suspended")