Asyncio如何使用run_forever?

11

我想做的事情:

  1. 创建一个asyncio事件循环
  2. 将该循环传递给系统中的各个类以安排协程
  3. 该循环还用于处理事件的响应(例如,我有一个队列,某些事件处理代码会将项目放在该队列上,并且等待该队列的get()来处理这些值的单独的协程)
  4. 有一个主线程“拥有”该循环,负责创建循环,并在系统关闭时取消循环上运行的所有任务,并停止循环(干净的关闭)

我的理解是由于第3点,需要有东西在循环上调用run_forever(),以确保任务可以安排在循环上。但是如果我调用run_forever(),那么我的主线程会阻塞,永远不会终止。

我的尝试:

生成一个线程,传入循环,然后在线程中调用run_forever。这意味着我的单元测试永远无法完成。要点如下:

def __start_background_loop(loop):
    def run_forever(loop):
        loop.run_forever()

    # because run_forever() will block the current thread, we spawn
    # a subthread to issue that call in.
    thread = Thread(target=run_forever, args=(loop,))
    thread.start()

def __end_background_loop(loop):
    for task in Task.all_tasks(loop):
        task.cancel()
    loop.stop()

为什么不一开始就在另一个线程中创建事件循环呢?asyncio.new_event_loop() - Zach Gates
1个回答

11

有两种可能的方法:您可以在主线程中运行事件循环,也可以在后台线程中运行。如果您在主线程中运行它,那么您需要在程序初始化的最后一步运行run_forever(或者run_until_complete(main()) 或等效的方式)。在这种情况下,主线程会“阻塞”,但这没关系,因为其事件循环将保持活动状态并响应外部事件,从而使程序正常工作。调用分派协程和回调的单个“阻塞”事件循环是 asyncio 的设计方式。

在一些情况下,例如包含大量同步代码或已经在多个线程间通信的程序中,创建一个专用线程并在其中运行事件循环通常是更好的选择。此时,您必须非常小心,除了使用调用 loop.call_soon_threadsafe()asyncio.run_coroutine_threadsafe() 之外,不要与事件循环进行任何其他交互。例如,必须使用 loop.call_soon_threadsafe(__end_background_loop) 调用 __end_background_loop,因为它会与任务和事件循环进行交互。对于所有与事件循环的交互都适用 - 例如,从另一个线程调用 loop.stop() 是不允许的,必须拼写为 loop.call_soon_threadsafe(loop.stop) 。当然,从 asyncio 回调函数和协程调用循环函数是可以的,因为这些将始终在事件循环运行的相同线程中运行。

1
如果从另一个线程调用loop.stop()甚至都不安全,那么该怎样停止循环呢?或者你的意思是说,从另一个线程调用call_soon_threadsafe(loop.stop())是安全的,但直接调用loop.stop()则不安全? - Adam Parkin
3
@AdamParkin 循环可以从回调或协程中停止,在事件循环线程中 - 例如,协程可以检测到套接字上的“退出”命令并调用loop.stop()。当画面中只有一个线程,并且它只运行事件循环时,事情就是这样工作的,这是asyncio的完全有效(甚至典型)用法。如果您使用多个线程,则所有其他线程都必须调用loop.stop()作为loop.call_soon_threadsafe(loop.stop)(注意在loop.stop后缺少括号),并且与循环相关的任何其他内容也是如此。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接