简体   繁体   English

Python asyncio run_coroutine_threadsafe永不运行协程?

[英]Python asyncio run_coroutine_threadsafe never running coroutine?

I'm not sure what I'm doing wrong here, I'm trying to have a class which contains a queue and uses a coroutine to consume items on that queue. 我不确定我在这里做错了什么,我正在尝试创建一个包含队列并使用协程消耗该队列中项目的类。 The wrinkle is that the event loop is being run in a separate thread (in that thread I do loop.run_forever() to get it running). loop.run_forever()是,事件循环正在单独的线程中运行(在该线程中,我执行loop.run_forever()使其运行)。

What I'm seeing though is that the coroutine for consuming items is never fired: 我所看到的是,用于消费物品的协程从未被解雇:

import asyncio
from threading import Thread

import functools

# so print always flushes to stdout
print = functools.partial(print, flush=True)


def start_loop(loop):
    def run_forever(loop):
        print("Setting loop to run forever")
        asyncio.set_event_loop(loop)
        loop.run_forever()
        print("Leaving run forever")

    asyncio.set_event_loop(loop)
    print("Spawaning thread")
    thread = Thread(target=run_forever, args=(loop,))
    thread.start()


class Foo:
    def __init__(self, loop):
        print("in foo init")
        self.queue = asyncio.Queue()
        asyncio.run_coroutine_threadsafe(self.consumer(self.queue), loop)

    async def consumer(self, queue):
        print("In consumer")
        while True:
            message = await queue.get()
            print(f"Got message {message}")
            if message == "END OF QUEUE":
                print(f"exiting consumer")
                break
            print(f"Processing {message}...")


def main():
    loop = asyncio.new_event_loop()
    start_loop(loop)
    f = Foo(loop)
    f.queue.put("this is a message")
    f.queue.put("END OF QUEUE")

    loop.call_soon_threadsafe(loop.stop)

    # wait for the stop to propagate and complete
    while loop.is_running():
        pass


if __name__ == "__main__":
    main()

Output: 输出:

Spawaning thread
Setting loop to run forever
in foo init
Leaving run forever

There are several issues with this code. 此代码存在多个问题。

First, check the warnings: 首先,检查警告:

test.py:44: RuntimeWarning: coroutine 'Queue.put' was never awaited
f.queue.put("this is a message")
test.py:45: RuntimeWarning: coroutine 'Queue.put' was never awaited
f.queue.put("END OF QUEUE")

That means queue.put is a coroutine, so it has to be run using run_coroutine_threadsafe : 这意味着queue.put是协程,因此必须使用run_coroutine_threadsafe运行:

asyncio.run_coroutine_threadsafe(f.queue.put("this is a message"), loop)
asyncio.run_coroutine_threadsafe(f.queue.put("END OF QUEUE"), loop)

You could also use queue.put_nowait which is a synchronous method. 您也可以使用queue.put_nowait这是一个同步方法。 However, asyncio objects are generally not threadsafe so every synchronous call has to go through call_soon_threadsafe : 但是,异步对象通常不是线程安全的,因此每个同步调用都必须通过call_soon_threadsafe进行

loop.call_soon_threadsafe(f.queue.put_nowait, "this is a message")
loop.call_soon_threadsafe(f.queue.put_nowait, "END OF QUEUE")

Another issue is that the loop gets stopped before the consumer task can start processing items. 另一个问题是,在使用者任务可以开始处理项目之前,循环已停止。 You could add a join method to the Foo class to wait for the consumer to finish: 您可以向Foo类添加一个join方法,以等待使用者完成:

class Foo:
    def __init__(self, loop):
        [...]
        self.future = asyncio.run_coroutine_threadsafe(self.consumer(self.queue), loop)

    def join(self):
        self.future.result()

Then make sure to call this method before stopping the loop: 然后确保在停止循环之前调用此方法:

f.join()
loop.call_soon_threadsafe(loop.stop)

This should be enough to get the program to work as you expect. 这应该足以使程序按预期工作。 However, this code is still problematic on several aspects. 但是,此代码在几个方面仍然存在问题。

First, the loop should not be set both in the main thread and the extra thread. 首先,不应同时在主线程和额外线程中设置循环。 Asyncio loops are not meant to be shared between threads, so you need to make sure that everything asyncio related happens in the dedicated thread. Asyncio循环不是要在线程之间共享,因此您需要确保与asyncio相关的所有事情都在专用线程中发生。

Since Foo is responsible for the communication between those two threads, you'll have to be extra careful to make sure every line of code runs in the right thread. 由于Foo负责这两个线程之间的通信,因此您必须格外小心,以确保每一行代码都在正确的线程中运行。 For instance, the instantiation of asyncio.Queue has to happen in the asyncio thread. 例如, asyncio.Queue的实例化必须发生在asyncio线程中。

See this gist for a corrected version of your program. 有关程序的正确版本,请参见此要点


Also, I'd like to point out that this is not the typical use case for asyncio. 另外,我想指出,这不是异步的典型用例。 You generally want to have an asyncio loop running in the main thread, especially if you need subprocess support : 您通常希望在主线程中运行asyncio循环,尤其是在需要子流程支持的情况下

asyncio supports running subprocesses from different threads, but there are limits: asyncio支持从不同线程运行子进程,但是有一些限制:

  • An event loop must run in the main thread 事件循环必须在主线程中运行
  • The child watcher must be instantiated in the main thread, before executing subprocesses from other threads. 在从其他线程执行子进程之前,必须在主线程中实例化子监视程序。 Call the get_child_watcher() function in the main thread to instantiate the child watcher. 在主线程中调用get_child_watcher()函数以实例化子监视程序。

I would suggest designing your application the other way, ie running asyncio in the main thread and use run_in_executor for the synchronous blocking code. 我建议您以另一种方式设计应用程序,即在主线程中运行asyncio并将run_in_executor用于同步阻塞代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM