基于它是否被再次调用,在协程中有条件?
我正试图将这个关键的“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 Event
, Queue
和Condition
但我不清楚如何使用它们。 你将如何使用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_task
和asyncio.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')
。 main
和handle_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')
并开始处理新的密钥。 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_task
将handle_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?