簡體   English   中英

Python asyncio run_coroutine_threadsafe永不運行協程?

[英]Python asyncio run_coroutine_threadsafe never running coroutine?

我不確定我在這里做錯了什么,我正在嘗試創建一個包含隊列並使用協程消耗該隊列中項目的類。 loop.run_forever()是,事件循環正在單獨的線程中運行(在該線程中,我執行loop.run_forever()使其運行)。

我所看到的是,用於消費物品的協程從未被解雇:

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

輸出:

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

此代碼存在多個問題。

首先,檢查警告:

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

這意味着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)

您也可以使用queue.put_nowait這是一個同步方法。 但是,異步對象通常不是線程安全的,因此每個同步調用都必須通過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")

另一個問題是,在使用者任務可以開始處理項目之前,循環已停止。 您可以向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()

然后確保在停止循環之前調用此方法:

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

這應該足以使程序按預期工作。 但是,此代碼在幾個方面仍然存在問題。

首先,不應同時在主線程和額外線程中設置循環。 Asyncio循環不是要在線程之間共享,因此您需要確保與asyncio相關的所有事情都在專用線程中發生。

由於Foo負責這兩個線程之間的通信,因此您必須格外小心,以確保每一行代碼都在正確的線程中運行。 例如, asyncio.Queue的實例化必須發生在asyncio線程中。

有關程序的正確版本,請參見此要點


另外,我想指出,這不是異步的典型用例。 您通常希望在主線程中運行asyncio循環,尤其是在需要子流程支持的情況下

asyncio支持從不同線程運行子進程,但是有一些限制:

  • 事件循環必須在主線程中運行
  • 在從其他線程執行子進程之前,必須在主線程中實例化子監視程序。 在主線程中調用get_child_watcher()函數以實例化子監視程序。

我建議您以另一種方式設計應用程序,即在主線程中運行asyncio並將run_in_executor用於同步阻塞代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM