【问题标题】:Is it possible to run only a single step of the asyncio event loop是否可以只运行异步事件循环的一个步骤
【发布时间】:2015-04-21 20:22:24
【问题描述】:

我正在使用 asyncio 和 tkinter 开发一个简单的图形网络应用程序。我遇到了将异步事件循环与 Tk 的主循环结合起来的问题。如果可能的话,我想在没有线程的情况下这样做,因为这两个库(尤其是 tkinter)都不是线程安全的。目前,我在 asyncio 协程中使用 Tk.update,它只运行 tk 事件循环的单次迭代:

@asyncio.coroutine
def run_tk(tk, interval=0.1):
    try:
        while True:
            tk.update()
            yield from asyncio.sleep(interval)
    except TclError as e:
        if "application has been destroyed" not in e.args[0]:
            raise

但是,为了探索所有选项,我想知道是否可以反过来——是否可以在 tk 回调中仅调用异步事件循环的单次迭代。

【问题讨论】:

  • 你可以combine the Tk main loop with asyncio,但我不知道这是否仍然允许你处理网络流量(即网络事件如何流入Tk主循环)?
  • 它似乎可以工作,但它作弊 - 它创建了一个 TkEventLoop,它基本上在循环中运行 update,但也创建了一个传统的事件循环来执行网络 i/o 并运行它在一个线程中。这是两全其美的;唯一的好处是直接回调(loop.call_later)在 Tk 事件循环中直接调用。
  • 我在问题和答案中遗漏了一个最小的工作示例。
  • 接受的答案显示了如何运行事件循环的单个步骤。您应该能够在 tkinter 主循环中反复调用 run_once 以“运行”asyncio。
  • loop._run_once()。但是,如果您在另一个线程上运行 asyncio 循环时坚持使用它自己的线程,则没有问题。大多数 asyncio 也不是线程安全的。您可以分别使用 loop.run_coroutine_threadsafe()loop.call_soon_threadsafe() 从 tkinter 线程调度协程和回调。

标签: python events tkinter tk python-asyncio


【解决方案1】:

loop.run_once() 这样的公共方法的缺失是故意的。 并非每个受支持的事件循环都有迭代一个步骤的方法。通常底层 API 有创建事件循环并永远运行它的方法,但模拟单步可能非常无效。

如果你真的需要它,你可以轻松实现单步迭代:

import asyncio


def run_once(loop):
    loop.call_soon(loop.stop)
    loop.run_forever()


loop = asyncio.get_event_loop()

for i in range(100):
    print('Iteration', i)
    run_once(loop)

【讨论】:

  • 等等……什么? run_forever 如何只运行一次?这有什么意义?
  • @cloudformdesign 查看我的回答 here 以获得解释。
  • 那是我的问题!谢谢!
  • 在检查了官方文档 (here) 之后,我确认这实际上是执行此操作的“规范”方式。谢谢!
【解决方案2】:

看看这个例子。

import asyncio
from tkinter import *

class asyncTk(Tk):
    def __init__(self):
        super().__init__()
        self.running = True
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def on_closing(self):
        self.running = False
        self.destroy()
        
    def __await__(self):
        while self.running:
            self.update()
            yield

async def asd():
    for x in range(1,10):
        await asyncio.sleep(1)
        print(x)

async def main():
    w = asyncTk()
    asyncio.create_task(asd())
    await w

asyncio.run(main())

【讨论】:

  • 这可行,但我的问题已经涵盖了它的可能性。我问的是使用 Tk 来驱动事件循环而不是 asyncio。
【解决方案3】:

我使用以下过程创建自己的run_once()run_forever()

这是一个简化的例子:

import asyncio

async def worker(**kwargs):
    id = kwargs.get('id', '0.0.0.0.0.0')
    time = kwargs.get('time', 1)

    try:
        # Do stuff.
        print('start: ' + id)
    finally:
        await asyncio.sleep(time)

async def worker_forever(**kwargs):
    while True:
        await worker(**kwargs)

def init_loop(configs, forever=True):
    loop = asyncio.get_event_loop()

    if forever:
        tasks = [
            loop.create_task(worker_forever(id=conf['id'], time=conf['time'])) 
            for conf in config
        ]

    else:
        tasks = [
            asyncio.ensure_future(worker(id=conf['id'], time=conf['time'])) 
            for conf in configs
        ]

    return loop, tasks

def run_once(configs):
    print('RUN_ONCE')
    loop, futures = init_loop(configs, forever=False)
    result = loop.run_until_complete(asyncio.gather(*futures))
    print(result)

def run_forever(configs):
    print('RUN_FOREVER')
    loop, _ = init_loop(configs, forever=True)
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        print("Closing Loop")
        loop.close()

if __name__ == '__main__':
    configurations = [
        {'time': 5, 'id': '4'},
        {'time': 6, 'id': '5'},
        {'time': 1, 'id': '6'},
    ]  # TODO :: DUMMY

    run_once(configurations)
    run_forever(configurations)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-10
    • 1970-01-01
    • 2017-12-19
    • 2017-12-26
    • 1970-01-01
    • 2018-11-16
    • 2023-03-13
    • 1970-01-01
    相关资源
    最近更新 更多