是否可以 运行 只有异步事件循环的一个步骤

Is it possible to run only a single step of the asyncio event loop

我正在使用 asyncio 和 tkinter 开发一个简单的图形网络应用程序。我 运行 遇到了将 asyncio 事件循环与 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 回调中仅调用 asyncio 事件循环的单次迭代。

loop.run_once()这样的public方法的缺失是故意的。 并非每个受支持的事件循环都有迭代一个步骤的方法。通常底层 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_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)

看看这个例子。

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())