如何 运行 上下文中的协程?

How to run a coroutine inside a context?

In the Python docs about Context Vars Context::run 方法被描述为能够在上下文中执行可调用,因此对上下文的可调用执行的更改包含在复制的上下文中。如果你需要执行协程怎么办?为了实现相同的行为,您应该怎么做?

在我的例子中,我想要的是这样的东西来处理具有可能的嵌套事务的事务上下文:

my_ctxvar = ContextVar("my_ctxvar")

async def coro(func, transaction):
    token = my_ctxvar.set(transaction)
    r = await func()
    my_ctxvar.reset(token)  # no real need for this, but why not either
    return r

async def foo():
    ctx = copy_context()
    # simplification to one case here: let's use the current transaction if there is one
    if tx_owner := my_ctxvar not in ctx:
        tx = await create_transaction()
    else:
        tx = my_ctxvar.get()
    
    try:
        r = await ctx.run(coro)  # not actually possible
        if tx_owner:
            await tx.commit()
    except Exception as e:
        if tx_owner:
            await tx.rollback()
        raise from e
    return r

正如我已经指出的 context variablesasyncio 原生支持,无需任何额外配置即可使用。 需要注意的是:

  • 当前任务通过await执行的oroutines 共享相同的上下文
  • create_task 生成的新任务在父任务上下文的副本 中执行。

因此,为了在当前上下文的副本中执行协程,您可以将其作为任务执行:

await asyncio.create_task(coro())

小例子:

import asyncio
from contextvars import ContextVar

var = ContextVar('var')


async def foo():
    await asyncio.sleep(1)
    print(f"var inside foo {var.get()}")
    var.set("ham")  # change copy


async def main():
    var.set('spam')
    await asyncio.create_task(foo())
    print(f"var after foo {var.get()}")


asyncio.run(main())
var inside foo spam
var after foo spam