运行 异步事件循环中的无限循环 运行 关闭主线程

run infinite loop in asyncio event loop running off the main thread

我编写的代码似乎可以满足我的要求,但我不确定这是否是个好主意,因为它将线程和事件循环混合到 运行 主线程之外的无限循环。这是一个最小的代码片段,捕捉了我正在做的事情的想法:

import asyncio
import threading

msg = ""

async def infinite_loop():
    global msg
    while True:
        msg += "x"
        await asyncio.sleep(0.3)

def worker():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    asyncio.get_event_loop().run_until_complete(infinite_loop())

t = threading.Thread(target=worker, daemon=True)
t.start()

主要思想是我有一个无限循环,每 0.3 秒处理一个全局变量。我希望这个无限循环到 运行 离开主线程,这样我仍然可以访问主线程中的共享变量。这在 jupyter 中特别有用,因为如果我在主线程中调用 run_until_complete 我就不能再与 jupyter 交互了。我希望主线程可用于交互式访问和修改 msg。在我的示例中使用异步似乎没有必要,但我使用的库具有 运行 服务器的异步代码,因此这是必要的。我是 python 中异步和线程的新手,但我记得在某处读到/听到过将线程与 asyncio 一起使用是自找麻烦……这是个坏主意吗?我的方法是否存在任何潜在的并发问题?

I'm new to async and threading in python, but I remember reading / hearing somewhere that using threading with asyncio is asking for trouble...

不鼓励初学者混合使用 asyncio 和线程,因为它会导致不必要的复杂化,并且通常源于对如何正确使用 asyncio 缺乏理解。刚接触 asyncio 的程序员通常会习惯性地使用线程,将它们用于协程更适合的任务。

但是,如果您有充分的理由生成一个 运行 异步事件循环的线程,一定要这样做 - 没有什么需要异步事件循环是 运行在主线程中。请注意仅从 运行 事件循环的线程与事件循环本身交互(调用 call_sooncreate_taskstop 等方法) ,即来自 asyncio 协程和回调。要安全地与其他线程(例如您的主线程)的事件循环交互,请使用 loop.call_soon_threadsafe() or asyncio.run_coroutine_threadsafe().

请注意,设置全局变量等不算作 "interacting",因为 asyncio 不会观察到这些。当然,由您来处理 inter-thread 同步问题,例如使用锁保护对复杂可变结构的访问。

is this a bad idea?

如果不确定是否混合线程和asyncio,你可以问自己两个问题:

  • 考虑到 asyncio 提供 运行 并行和 run_in_executor 等待阻塞代码的协程,我什至需要线程吗?
  • 如果我有提供并行性的线程,我真的需要 asyncio 吗?

你的问题对这两个问题都提供了很好的答案——你需要线程以便主线程可以与 jupyter 交互,你需要 asyncio 因为你依赖于使用它的库。

Are there any potential concurrency issues with my approach?

GIL 确保在一个线程中设置全局变量并在另一个线程中读取它不会发生数据竞争,因此您所展示的内容应该没问题。

如果添加显式同步,例如 multi-threaded 队列或条件变量,请记住同步代码不得阻塞事件循环。换句话说,您不能只等待 threading.Event in an asyncio coroutine because that would block all coroutines. Instead, you can await an asyncio.Event,然后使用另一个线程中的 loop.call_soon_threadsafe(event.set) 之类的东西来设置它。