Python 如何等待从另一个线程提交的回调完成?
我有两个Python线程共享一些状态,Python 如何等待从另一个线程提交的回调完成?,python,python-asyncio,Python,Python Asyncio,我有两个Python线程共享一些状态,A和B。在某一点上,A提交一个回调,由B在其循环上运行,如下所示: # This line is executed by A loop.call_soon_threadsafe(callback) 在此之后,我想继续执行其他操作,但我想确保在执行此操作之前,B已经运行了callback。除了标准线程同步原语外,是否有其他方法使A等待回调完成?我知道call\u soon\u threadsafe返回一个可以取消任务的asyncio.Handle对象,但我不
A
和B
。在某一点上,A
提交一个回调,由B
在其循环上运行,如下所示:
# This line is executed by A
loop.call_soon_threadsafe(callback)
在此之后,我想继续执行其他操作,但我想确保在执行此操作之前,B
已经运行了callback
。除了标准线程同步原语外,是否有其他方法使A
等待回调完成?我知道call\u soon\u threadsafe
返回一个可以取消任务的asyncio.Handle
对象,但我不确定这是否可以用于等待(我仍然不太了解asyncio
)
在这种情况下,此回调调用loop.close()
并取消剩余的任务,然后在B
中loop.run\u forever()
之后有一个loop.close()
。因此,对于这个用例,尤其是线程安全机制,它允许我从a
知道循环何时被有效关闭,这同样适用于我,不涉及互斥体/条件变量/etc
我知道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()
我已经用尝试过这种变体,但它不起作用,相反,线程
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()
回调已设置且(大部分)已忘记。它们不打算用于您需要从中获得结果的某些事情。这就是为什么生成的句柄只允许您取消回调(不再需要此回调),仅此而已
如果您需要在另一个线程中等待asyncio管理的协程的结果,请使用协程并将其作为任务进行调度;这将为您提供一个新的解决方案,然后您可以等待完成
但是,使用run\u coroutine\u threadsafe()
停止循环确实需要循环处理比实际运行多的一轮回调;由run\u coroutine\u threadsafe()
返回的Future
将不会被通知其计划的任务的状态更改。您可以通过在循环中运行asyncio.sleep(0)
来解决此问题。在关闭循环之前,在线程B中运行\u直到\u 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()
除了标准线程同步原语外,还有什么方法可以等待回调的完成
按照Martijn最初的建议,通常您会使用run\u coroutine\u 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\u soon\u threadsafe(callback)
,而是使用submit\u and\u wait(loop,callback)
。线程同步在那里,但完全隐藏在submit\u和
中,感觉就像是一个副本。可能相关:,@jdv:这些都没有使用asyncio。如果您使用的是线程,那么您实际上也在使用asyncio
来运行任务吗?或者您只是使用loop.call\u soon\u threadsafe(callback)
来处理某种与线程相关的任务<代码>循环。call_soon_threadsafe(callback)实际上只用于其他线程向asyncio
-循环管理的代码发送信号。好吧,@jdv谢谢,但这些不是用于多线程代码的。我刚才读到的是run_coroutine_threadsafe()
。因此,这将给我一个未来,我可以安全地等待,即使是从另一个线程,即使该任务实际上停止了循环?我在fut.running()时尝试了,但这似乎使线程a
立即完成。即使我在回调
,future.running()中添加了time.sleep(1)
(仅用于测试!我知道您应该使用asyncio.sleep
)
总是错误的…@jdehesa:我看到的问题是,停止循环也几乎保证了将信号从管理shutdown\u循环()的任务传递回线程B持有的Future()
的回调也会被取消。这里的连接被切断了。关闭循环还需要一点时间。。处理。@jdehesa:很抱歉,我已经计算出哪些回调会被清除,只需要在循环中运行一次。调度asyncio.sleep(0)
,一个虚拟的noop协同程序,与asyncio.run\u直到完成()
,足以清除这些回调。在关闭循环之前,在线程B上运行它。是的,它可以工作!正如你所说,为了清晰和可靠,我可能最终使用了基于线程的协调,但我很荣幸能找到一种不用它的方法。是的,这很有效,而且非常简单。我接受了另一个答案,因为它包括了一种不需要显式线程同步的解决方法,正如问题中所建议的,但最终可能也会使用类似的方法。