相关疑难解决方法(0)

asyncio实际上如何工作?

这个问题是由我的另一个问题推动的:如何在cdef中等待?

网上有大量的文章和博客文章asyncio,但它们都非常肤浅.我找不到任何有关如何asyncio实际实现的信息,以及I/O异步的原因.我试图阅读源代码,但它是成千上万行不是最高级别的C代码,其中很多处理辅助对象,但最重要的是,很难在Python语法和它将翻译的C代码之间建立连接成.

Asycnio自己的文档甚至没那么有用.没有关于它是如何工作的信息,只有关于如何使用它的一些指导,这些指导有时也会误导/写得很差.

我熟悉Go的coroutines实现,并且希望Python做同样的事情.如果是这种情况,我在上面链接的帖子中出现的代码就可以了.既然没有,我现在正试图找出原因.到目前为止我最好的猜测如下,请纠正我错在哪里:

  1. 表单的过程定义async def foo(): ...实际上被解释为类继承的方法coroutine.
  2. 也许,async def实际上是通过await语句拆分成多个方法,其中调用这些方法的对象能够跟踪到目前为止通过执行所做的进度.
  3. 如果上述情况属实,那么,从本质上讲,协程的执行归结为一些全局管理器调用协程对象的方法(循环?).
  4. 全局管理器以某种方式(如何?)了解I/O操作何时由Python(仅?)代码执行,并且能够在当前执行方法放弃控制之后选择一个待执行的协程方法执行(命中await语句) ).

换句话说,这是我尝试将某些asyncio语法"贬低"为更容易理解的东西:

async def coro(name):
    print('before', name)
    await asyncio.sleep()
    print('after', name)

asyncio.gather(coro('first'), coro('second'))

# translated from async def coro(name)
class Coro(coroutine):
    def before(self, name):
        print('before', name)

    def after(self, name):
        print('after', name)

    def __init__(self, name):
        self.name = name
        self.parts = self.before, self.after
        self.pos = 0

    def __call__():
        self.parts[self.pos](self.name)
        self.pos += 1

    def …
Run Code Online (Sandbox Code Playgroud)

python python-3.x python-asyncio

64
推荐指数
5
解决办法
1万
查看次数

asyncio 的默认调度程序什么时候公平?

据我了解,目的是同时asyncio.gather运行其参数,并且当协程执行等待表达式时,它为事件循环提供了安排其他任务的机会。考虑到这一点,我惊讶地发现以下代码片段忽略了.asyncio.gather

import asyncio                                                             
  
async def aprint(s):
    print(s)

async def forever(s):
    while True:
        await aprint(s)

async def main():
    await asyncio.gather(forever('a'), forever('b'))

asyncio.run(main())
Run Code Online (Sandbox Code Playgroud)

据我了解,会发生以下情况:

  1. asyncio.run(main()) 对事件循环进行任何必要的全局初始化并安排 main() 执行。
  2. main() 安排 asyncio.gather(...) 执行并等待其结果
  3. asyncio.gather 安排forever('a') 和forever('b') 的执行
  4. 无论其中哪一个先执行,它们都会立即等待 aprint() 并让调度程序有机会在需要时运行另一个协程(例如,如果我们从“a”开始,那么我们就有机会开始尝试评估“b”,这应该已安排执行)。
  5. 在输出中,我们将看到一串行,每行都包含“a”或“b”,并且调度程序应该足够公平,以便我们在足够长的时间内至少看到其中的一行。

实际上,这不是我所观察到的。相反,整个程序相当于while True: print('a'). 我发现非常有趣的是,即使对代码进行微小的更改似乎也会重新引入公平性。例如,如果我们使用以下代码,那么我们会在输出中得到大致相等的“a”和“b”混合。

async def forever(s):
    while True:
        await aprint(s)
        await asyncio.sleep(1.)
Run Code Online (Sandbox Code Playgroud)

验证它似乎与我们在无限循环中和在无限循环外花费的时间没有任何关系,我发现以下更改也提供了公平性。

async def forever(s):
    while True:
        await aprint(s)
        await asyncio.sleep(0.)
Run Code Online (Sandbox Code Playgroud)

有谁知道为什么会发生这种不公平现象以及如何避免它?我想,当有疑问时,我可以主动在各处添加一个空的睡眠语句,并希望这足够了,但对我来说,为什么原始代码的行为不符合预期,这对我来说非常不明显。

以防万一,因为 asyncio 似乎已经经历了相当多的 API 更改,我在 Ubuntu 机器上使用 Python 3.8.4 的普通安装。

python python-asyncio

5
推荐指数
1
解决办法
1370
查看次数

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

在有关 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 …
Run Code Online (Sandbox Code Playgroud)

python coroutine async-await python-asyncio python-contextvars

4
推荐指数
1
解决办法
277
查看次数