您如何等待其他线程提交的回调完成?

时间:2018-11-01 18:14:00

标签: python python-asyncio

我有两个共享某些状态的Python线程AB。某一时刻,A提交了一个由B在其循环中运行的回调,例如:

# This line is executed by A
loop.call_soon_threadsafe(callback)

此后,我想继续执行其他操作,但是在执行此操作之前,请确保callback已运行B。有什么方法(除了标准线程同步原语之外)可以使A等待回调完成?我知道call_soon_threadsafe返回了一个asyncio.Handle对象,该对象可以取消任务,但是我不确定该对象是否可以用于等待(我对asyncio还是不太了解)。 / p>

在这种情况下,此回调调用loop.close()并取消剩余的任务,此后,在B中,在loop.run_forever()之后有一个loop.close()。因此,对于这种用例,尤其是一种线程安全机制,允许我从A知道何时有效地关闭了循环,这对我也同样适用-再次,不涉及互斥锁/条件变量/等。

我知道asyncio并不是线程安全的,只有少数例外,但是我想知道是否提供了一种方便的方法来实现这一目标。


这里是我的意思的一小段,以防万一。

import asyncio
import threading
import time

def thread_A():
    print('Thread A')
    loop = asyncio.new_event_loop()
    threading.Thread(target=thread_B, args=(loop,)).start()
    time.sleep(1)
    handle = loop.call_soon_threadsafe(callback, loop)
    # How do I wait for the callback to complete before continuing?
    print('Thread A out')

def thread_B(loop):
    print('Thread B')
    asyncio.set_event_loop(loop)
    loop.run_forever()
    loop.close()
    print('Thread B out')

def callback(loop):
    print('Stopping loop')
    loop.stop()

thread_A()

我用asyncio.run_coroutine_threadsafe尝试了这种变化,但是它不起作用,而是线程A永远挂起。不知道我是在做错什么,还是因为我正在停止循环。

import asyncio
import threading
import time

def thread_A():
    global future
    print('Thread A')
    loop = asyncio.new_event_loop()
    threading.Thread(target=thread_B, args=(loop,)).start()
    time.sleep(1)
    future = asyncio.run_coroutine_threadsafe(callback(loop), loop)
    future.result()  # Hangs here
    print('Thread A out')

def thread_B(loop):
    print('Thread B')
    asyncio.set_event_loop(loop)
    loop.run_forever()
    loop.close()
    print('Thread B out')

async def callback(loop):
    print('Stopping loop')
    loop.stop()

thread_A()

2 个答案:

答案 0 :(得分:3)

设置了回调,并且(大部分)忘记了。它们不适用于您需要从中获得结果的东西。这就是为什么生成的句柄仅允许您取消回调(不再需要此回调),仅此而已。

如果您需要在另一个线程中等待异步管理的协程的结果,请使用协程并将其作为任务与asyncio.run_coroutine_threadsafe()计划;这会给您Future() instance,然后您可以等待完成。

但是,用run_coroutine_threadsafe()停止循环确实需要循环处理比实际运行多一轮的回调。由Future返回的run_coroutine_threadsafe()不会被通知其计划任务的状态变化。您可以通过在关闭循环之前在线程B中运行asyncio.sleep(0)loop.run_until_complete()来解决此问题:

def thread_A():
    # ... 
    # when done, schedule the asyncio loop to exit
    future = asyncio.run_coroutine_threadsafe(shutdown_loop(loop), loop)
    future.result()  # wait for the shutdown to complete
    print("Thread A out")

def thread_B(loop):
    print("Thread B")
    asyncio.set_event_loop(loop)
    loop.run_forever()
    # run one last noop task in the loop to clear remaining callbacks
    loop.run_until_complete(asyncio.sleep(0))
    loop.close()
    print("Thread B out")

async def shutdown_loop(loop):
    print("Stopping loop")
    loop.stop()

当然,这有点怪异,并且取决于回调管理和跨线程任务计划的内部结构是否保持不变。按照默认的asyncio实现而言,运行一次noop任务足以进行几轮回调,从而创建更多要处理的回调,但是替代循环实现可能会对此有所不同。

因此,对于关闭循环,使用基于线程的协调可能会更好:

def thread_A():
    # ...
    callback_event = threading.Event()
    loop.call_soon_threadsafe(callback, loop, callback_event)
    callback_event.wait()  # wait for the shutdown to complete
    print("Thread A out")

def thread_B(loop):
    print("Thread B")
    asyncio.set_event_loop(loop)
    loop.run_forever()
    loop.close()
    print("Thread B out")

def callback(loop, callback_event):
    print("Stopping loop")
    loop.stop()
    callback_event.set()

答案 1 :(得分:1)

  

有什么方法(除了标准线程同步原语之外)使A等待回调完成?

通常您会使用Martijn最初建议的run_coroutine_threadsafe。但是您使用loop.stop()会使回调有些特定。鉴于此,最好使用标准线程同步原语,它在这种情况下非常简单,可以与回调实现和代码的其余部分完全脱钩。例如:

def submit_and_wait(loop, fn, *args):
    "Submit fn(*args) to loop, and wait until the callback executes."
    done = threading.Event()
    def wrap_fn():
        try:
            fn(*args)
        finally:
            done.set()
    loop.call_soon_threadsafe(wrap_fn)
    done.wait()

使用loop.call_soon_threadsafe(callback),而不要使用submit_and_wait(loop, callback)。线程同步在那里,但是完全隐藏在submit_and_wait内部。