Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/313.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何等待从另一个线程提交的回调完成?_Python_Python Asyncio - Fatal编程技术网

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对象,但我不

我有两个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
对象,但我不确定这是否可以用于等待(我仍然不太了解
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上运行它。是的,它可以工作!正如你所说,为了清晰和可靠,我可能最终使用了基于线程的协调,但我很荣幸能找到一种不用它的方法。是的,这很有效,而且非常简单。我接受了另一个答案,因为它包括了一种不需要显式线程同步的解决方法,正如问题中所建议的,但最终可能也会使用类似的方法。