基于它是否被再次调用,在协程中有条件?

我正试图将这个关键的“debouncing”逻辑从Javascript翻译成Python。

function handle_key(key) {
    if (this.state == null) {
        this.state = ''
    }
    this.state += key
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => {
        console.log(this.state)
    }, 500)
}

handle_key('a')
handle_key('b')

这个想法是,随后的按键会延长超时时间。 Javascript版本打印:

ab 

我不想翻译JS超时函数,我宁愿有使用asyncio的惯用Python。 我在Python(3.5)中的尝试如下,但它不起作用,因为global_state实际上并没有在我期望的时候更新。

import asyncio

global_state = ''

@asyncio.coroutine
def handle_key(key):
    global global_state
    global_state += key
    local_state = global_state
    yield from asyncio.sleep(0.5)
    #if another call hasn't modified global_state we print it
    if local_state == global_state:
        print(global_state)

@asyncio.coroutine
def main():
    yield from handle_key('a')
    yield from handle_key('b')

ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(main())

它打印:

a
ab

我已经看过asyncio EventQueueCondition但我不清楚如何使用它们。 你将如何使用Python的asyncio实现所需的行为?

编辑

关于如何使用handle_keys更多细节。 我有一个异步函数来检查按键。

@asyncio.coroutine
def check_keys():
    keys = driver.get_keys()
    for key in keys:
        yield from handle_key(key)

这又是与其他程序任务一起安排的

@asyncio.coroutine
def main():
    while True:
        yield from check_keys()
        yield from do_other_stuff()

ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(main())

Qeek使用asyncio.create_taskasyncio.gather是有道理的。 但是,我将如何在这样的循环内使用它? 或者有另一种方法来安排允许handle_keys调用“重叠”的异步任务?

GitHub上的实际代码,如果你有兴趣。


怎么了

基本上yield from xy()yield from xy()与正常的函数调用非常相似。 函数调用和yield from之间的区别在于函数调用立即开始处理调用的函数。 语句插入的yield from将事件循环中的coroutine调用到队列中,并对事件循环进行控制,并决定处理其队列中的哪个协程。

以下是你的代码的解释:

  • 它将main添加到事件循环的队列中。
  • 事件循环开始处理队列中的协程。
  • 队列只包含main协程,所以它开始。
  • 代码yield from handle_key('a')
  • 它在事件循环的队列中添加handle_key('a')
  • 事件循环现在包含mainhandle_key('a')但是main不能启动,因为它正在等待handle_key('a')
  • 所以事件循环启动handle_key('a')
  • 它会做一些东西,直到它yield from asyncio.sleep(0.5)
  • 现在事件循环包含main()handle_key('a')sleep(0.5)
  • main()正在等待handle_key('a')
  • handle_key('a')正在等待sleep(0.5)结果sleep(0.5)
  • 睡眠没有依赖性,所以可以开始。
  • asyncio.sleep(0.5)在0.5秒后返回None
  • 事件循环采用None并将其返回到handle_key('a')协同程序中。
  • 返回值被忽略,因为它没有被分配到任何东西
  • handle_key('a')打印关键字(因为没有任何改变状态)
  • 最后的handle_key协程返回None(因为没有return语句)。
  • None返回到主。
  • 返回值也被忽略。
  • 代码yield from handle_key('b')并开始处理新的密钥。
  • 它从步骤5开始执行相同的步骤(但使用键b )。
  • 如何解决它

    main coroutinr取而代之:

    @asyncio.coroutine
    def main(loop=asyncio.get_event_loop()):
        a_task = loop.create_task(handle_key('a'))
        b_task = loop.create_task(handle_key('b'))
        yield from asyncio.gather(a_task, b_task)
    

    loop.create_taskhandle_key('a')handle_key('b')添加到事件循环的队列中,然后yield from asyncio.gather(a_task, b_task)将控制权交给事件循环。 从这一点开始的事件循环包含handle_key('a')handle_key('b')gather(...)main()

  • gather()结果的main() gather()
  • gather()等待,直到完成所有作为参数给出的任务
  • handle_key('a')handle_key('b')没有依赖关系,因此可以启动它们。
  • 事件循环现在包含2个协程,可以启动,但它会选择哪一个? 那么......谁知道这是实施依赖。 所以为了更好地模拟按键,这个替换应该更好一点:

    @asyncio.coroutine
    def main(loop=asyncio.get_event_loop()):
        a_task = loop.create_task(handle_key('a'))
        yield from asyncio.sleep(0.1)
        b_task = loop.create_task(handle_key('b'))
        yield from asyncio.gather(a_task, b_task)
    

    Python 3.5奖金

    从文档:

    与asyncio一起使用的协程可以使用async def语句来实现。

    coroutine的异步def类型是在Python 3.5中添加的,如果不需要支持较老的Python版本,那么推荐使用它。

    这意味着你可以替换:

    @asyncio.coroutine
    def main():
    

    与更新的声明

    async def main():
    

    如果你开始使用新的语法,那么你还必须用await替换yield from


    为什么你的代码现在无法工作?

    这两个handle_key javascript函数都不会阻止执行。 每个清除超时回调并设置新的。 它立即发生。

    协同程序工作的另一种方法:使用yield from或较新的语法await的协同程序,意味着我们只希望这个协同程序后恢复执行流程,如果完全实现:

    async def a():
        await asyncio.sleep(1)
    
    async def main():
        await a()
        await b()  # this line would be reached only after a() done - after 1 second delay
    

    代码中的asyncio.sleep(0.5) - 不是通过超时设置回调,而是应该在handle_key放完之前handle_key代码。

    让我们试着让代码工作

    你可以创建任务来开始执行一些协同程序“在后台”。 如果你不想完成任务,你也可以取消任务(就像你使用clearTimeout(this.timeout) )一样。

    模拟您的JavaScript片段的Python版本:

    import asyncio
    from contextlib import suppress
    
    global_state = ''
    timeout = None
    
    async def handle_key(key):
        global global_state, timeout
    
        global_state += key
    
        # cancel previous callback (clearTimeout(this.timeout))
        if timeout:
            timeout.cancel()
            with suppress(asyncio.CancelledError):
                await timeout
    
        # set new callback (this.timeout = setTimeout ...)
        async def callback():
            await asyncio.sleep(0.5)
            print(global_state)
        timeout = asyncio.ensure_future(callback())
    
    
    async def main():
        await handle_key('a')
        await handle_key('b')
    
        # both handle_key functions done, but task isn't finished yet
        # you need to await for task before exit main() coroutine and close loop
        if timeout:
            await timeout
    
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()
    

    成语?

    虽然上面的代码工作,但不应该如何使用asyncio 。 您的JavaScript代码基于回调,而asyncio通常会避免使用回调。

    很难证明你的例子有差异,因为它是基于本质的回调(键处理 - 是某种全局回调),没有更多的异步逻辑。 但是,稍后您将添加更多异步操作时,这种理解将非常重要。

    现在,我建议你阅读有关async / await在现代的JavaScript(它类似于Python的async / await ),并期待在比较它的回调/承诺的例子。 这篇文章看起来不错。

    它将帮助您了解如何在Python中使用基于协同的方法。

    UPD:

  • 由于buttons.check需要定期调用driver.get_buttons()你必须使用循环。 但它可以随着事件循环一起完成任务。

    如果你有某种button_handler(callback) (这通常是不同的库允许处理用户输入),你可以直接使用它来设置一些asyncio.Future并避免循环。

  • 考虑从一开始就用asyncio编写一些小gui应用程序。 我认为这可能会帮助您更好地了解如何改编现有项目。

  • 这里有一些伪代码,显示后台任务来处理按钮和使用asyncio来处理一些简单的UI事件/状态逻辑:

  • import asyncio
    from contextlib import suppress
    
    
    # GUI logic:
    async def main():
        while True:
            print('We at main window, popup closed')
    
            key = await key_pressed
            if key == 'Enter':
                print('Enter - open some popup')
    
                await popup()
                # this place wouldn't be reached until popup is not closed
    
                print('Popup was closed')
    
            elif key == 'Esc':
                print('Esc - exit program')
                return
    
    
    async def popup():
        while True:
            key = await key_pressed
            if key == 'Esc':
                print('Esc inside popup, let us close it')
                return
            else:
                print('Non escape key inside popup, play sound')
    
    
    # Event loop logic:
    async def button_check():
        # Where 'key_pressed' is some global asyncio.Future
        # that can be used by your coroutines to know some key is pressed
        while True:
            global key_pressed
            for key in get_buttons():
                key_pressed.set_result(key)
                key_pressed = asyncio.Future()
            await asyncio.sleep(0.01)
    
    
    def run_my_loop(coro):
        loop = asyncio.get_event_loop()
    
        # Run background task to process input
        buttons_task = asyncio.ensure_future(button_check())
    
        try:
            loop.run_until_complete(main())
        finally:
    
            # Shutdown task
            buttons_task.cancel()
            with suppress(asyncio.CancelledError):
                loop.run_until_complete(buttons_task)
    
            loop.close()
    
    
    if __name__ == '__main__':
        run_my_loop(main())
    
    链接地址: http://www.djcxy.com/p/53229.html

    上一篇: Conditional in a coroutine based on whether it was called again?

    下一篇: How can I wrap a synchronous function in an async coroutine?