【问题标题】:chaining coroutines in asyncio (and observer pattern)在异步(和观察者模式)中链接协程
【发布时间】:2015-03-15 20:05:20
【问题描述】:

我很难理解协程是如何链接在一起的。在一个比 hello world 或 factorials 稍微不那么琐碎的例子中,我想要一个循环来持续监视文件修改时间,然后在文件被触摸时打印出时间:

#!/usr/bin/env python3
import os
import asyncio

@asyncio.coroutine
def pathmonitor(path):
    modtime = os.path.getmtime(path)
    while True:
        new_time = os.path.getmtime(path)
        if new_time != modtime:
            modtime = new_time
            yield modtime
        yield from asyncio.sleep(1)

@asyncio.coroutine
def printer():
    while True:
        modtime = yield from pathmonitor('/home/users/gnr/tempfile')
        print(modtime)

loop = asyncio.get_event_loop()
loop.run_until_complete(printer())
loop.run_forever()

我希望这可以工作 - 但是,当我运行它时,我得到:

RuntimeError: Task got bad yield: 1426449327.2590399

我在这里做错了什么?

更新:关于观察者模式的示例,请参阅下面的答案(即,在文件被触及时有效地允许多个注册者获取更新)而不使用回调(您必须使用任务)。

UPDATE2:有一个更好的解决方法:3.5 的async for(异步迭代器):https://www.python.org/dev/peps/pep-0492/

【问题讨论】:

    标签: python python-3.x python-asyncio


    【解决方案1】:

    我通过在链式协程中使用 return 而不是 yield 使您的代码正常工作,就像 chained coroutines example 一样:

    #!/usr/bin/env python3
    import os
    import asyncio2
    
    @asyncio.coroutine
    def pathmonitor(path):
        modtime = os.path.getmtime(path)
        while True:
            new_time = os.path.getmtime(path)
            if new_time != modtime:
                modtime = new_time
                return modtime
            yield from asyncio.sleep(1)
    
    
    @asyncio.coroutine
    def printer():
        while True:
            modtime = yield from pathmonitor('/tmp/foo.txt')
            print(modtime)
    
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(printer())
    loop.run_forever()
    

    注意printer() 的循环将为每次迭代创建一个新的pathmonitor 生成器。不确定这是否是您的想法,但这可能是一个开始。

    我发现协程 API 和语法让我自己有些困惑。以下是我认为有帮助的一些阅读材料:

    【讨论】:

    • 感谢您提供有用的答案-在查看了更多文档后,我注意到他们声明协程只能返回或从中产生-不允许简单的产生。我想知道为什么协程不能像普通生成器那样使用 yield。
    • 是的,很好奇。您可以将pathmonitor 重写为常规生成器并将睡眠移动到printer。但我猜你想要一个链式协程。
    • 啊,这是一个有趣的想法——我会尝试一下,看看我能想出什么。我有库代码,它使用回调来通知文件何时被触摸,我想看看如何使它与协程(或没有回调)一起工作。我会有一个打印机功能、一个记录器功能或一个套接字功能,当文件被触摸时,它们都可能会做不同的事情。
    • @gnr 你不希望pathmonitor 成为生成器;协程和生成器不是一回事,不能互换。虽然生成器可能同时使用yield(正常情况)或yield from(如果它需要从子生成器中产生所有值),但asyncio 协程仅设计为使用yield from,因为yield from 可以用于从子生成器中获取实际返回的值。 ayield subgenerator() 调用将始终返回一个生成器对象。
    【解决方案2】:

    正如其他人指出的那样,我的错误是我试图使用像生成器这样的协程。我需要创建多个协程,而不是依赖生成器进行迭代。另外,我需要使用任务来实现没有回调的观察者模式,因为多个注册者可以yield from 同一个任务。我的路径监视器看起来像这样:

    import os
    import asyncio
    
    class PathInfo:
    
        def __init__(self, path):
            self.path = path
            self.modtime = os.path.getmtime(path)
            self.startTask()
    
        def startTask(self):
            self.task = asyncio.async(self._checkIfTouched())
    
        def _checkIfTouched(self):
            while True:
                yield from asyncio.sleep(1)
                newtime = os.path.getmtime(self.path)
                if self.modtime != newtime:
                    self.modtime = newtime
                    return newtime
    
    class PathMonitor:
    
        def __init__(self):
            self._info = {}
    
        @asyncio.coroutine
        def wasTouched(self, path):
            try:
                info = self._info[path]
            except KeyError:
                self._info[path] = info = PathInfo(path)
            if info.task.done():
                info.startTask()
            modtime = yield from info.task
            return modtime
    
    def printer():
        while True:
            modtime = yield from mon.wasTouched('/tmp/myfile')
            print(modtime)
    
    mon = PathMonitor()
    
    loop = asyncio.get_event_loop()
    asyncio.async(printer())
    loop.run_forever()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-12
      • 2016-07-06
      • 1970-01-01
      • 2016-02-20
      • 2023-04-10
      • 1970-01-01
      • 2023-03-16
      • 1970-01-01
      相关资源
      最近更新 更多