В Python есть функция contextvars.copy_context() , которая позволяет хранить копии текущих значений всех ContextVars
, которые в данный момент активны. Позже вы сможете запустить все эти активные значения, вызвав context_returned_by_copy_context.run(func). Однако метод run
является синхронизированным и ожидает обычного вызова, а не асинхронного. Как запустить асинхронную функцию с контекстом, возвращаемым copy_context
?
@lord_haffi Я знаю об этой общей проблеме, но я подумал, что в этом конкретном случае должна быть какая-то общая работа, поскольку важным моментом использования ContextVar является то, что они работают в асинхронных функциях, и привлекательность copy_context (я думал?) в первую очередь запускайте задачу с контекстом другой задачи; это кажется странным упущением
Да, но ContextVar
не предназначены для использования с асинхронными функциями. Они также широко используются с многопоточностью или многопроцессорностью. Кроме того, я не думаю, что существует общий обходной путь для этой проблемы, поскольку это сильно зависит от того, где вы запускаете объект контекста. Например. если вы выполняете context_returned_by_copy_context.run(func)
из работающего цикла событий, вам придется добавить его в цикл событий. Если нет, то вам придется создать новый. Но эти два случая ведут себя по-разному. Поэтому я не думаю, что у этого есть «общее» решение.
Предложение lord_haffi уместно, но нет необходимости создавать новый цикл событий, поскольку он уже должен быть запущен:
import asyncio
import contextvars
x = contextvars.ContextVar('x')
async def async_fn():
"""An aysnc coroutine we would like to run with contextvars.Contex.run"""
print(x.get())
def fn():
""" A non-coroutine that we will run with contextvars.Context.run"""
loop = asyncio.get_running_loop()
return loop.create_task(async_fn())
async def demo(value):
x.set(value)
ctx = contextvars.copy_context()
await ctx.run(fn)
async def main():
await asyncio.gather(demo(1), demo(2))
asyncio.run(main())
Распечатки:
1
2
В целом я с вами согласен, но я бы не сказал, что всегда существует работающий цикл событий. Вы можете комбинировать варианты использования многопоточности и многопроцессорности с asyncio. Фактически, я недавно реализовал сервер FastAPI, используя многопроцессорность и асинхронность для конечной точки, поскольку он создает тяжелую задачу как для ЦП, так и для сети, плюс мне нужно было «разблокировать» цикл событий FastAPI. Но цикл событий в каждом процессе создается только в определенный момент, когда начинается тяжелая часть сети. Просто для примера :)
@lord_haffi Я не говорил и даже не подразумевал, что цикл событий вообще работает всегда. Я сказал, что уже нужно работать, исходя из этой конкретной ситуации, когда не-сопрограмма вызывается из сопрограммы (для которой должен быть работающий цикл).
Ну, я не был уверен, выполнял ли ОП это из сопрограммы. Хотя это имело бы большой смысл, исходя из того, как был задан вопрос. Если это так, то вы, конечно, абсолютно правы.
Это очень близко к тому, что я в итоге сделал
Ваша проблема: «Как выполнить асинхронный код из контекста синхронизации». Есть два варианта: 1. Создать новый цикл событий, который выполняет асинхронную функцию (как задачу). 2. Добавьте асинхронную функцию (как задачу) в уже запущенный цикл событий.