4

在关于 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
4

1 回答 1

5

正如我在这里已经指出的那样,context variables它本身就支持asyncio并且可以在没有任何额外配置的情况下使用。应当指出的是:

  • 当前任务通过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
于 2020-11-24T15:39:33.143 回答