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

Rod*_*ins 4 python coroutine async-await python-asyncio python-contextvars

在有关 Context Vars 的 Python 文档中,描述了 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
Run Code Online (Sandbox Code Playgroud)

ale*_*ame 5

正如我在这里已经指出的那样,context variables本机支持asyncio并且无需任何额外配置即可使用。应该注意的是:

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

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

await asyncio.create_task(coro())
Run Code Online (Sandbox Code Playgroud)

小例子:

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())
Run Code Online (Sandbox Code Playgroud)
var inside foo spam
var after foo spam
Run Code Online (Sandbox Code Playgroud)