从运行线程在其他线程上调用协程

时间:2018-06-28 20:09:04

标签: python multithreading python-multithreading python-asyncio

我要替换现有程序的一部分。该原始程序使用线程。有一个从threading.Thread继承的特定类,我需要替换该功能,但我需要保持接口不变。

我要集成的功能打包在一个经常使用asyncio的库中。

我要替换的类的原始调用是这样的:

network = Network()
network.start()

network.fetch_something()  # crashes!

network.stop()

我已经到了我的替换类也继承自threading.Thread的地步,并且可以通过客户端库从run方法内部连接到后端:

class Network(threading.Thread):
     def __init__(self):
         self._loop = asyncio.new_event_loop()
         self._client = Client()  # this is the library

     def run(self):
         self._loop.run_until_complete(self.__connect())  # works dandy, implementation not shown
         self._loop.run_forever()

     def fetch_something(self):
         return self._loop.run_until_complete(self._client.fetch_something())

运行此代码将引发异常:

RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one

我有点明白这里发生了什么。在run方法中,事情得以解决,因为运行事件循环的同一线程是调用者。在另一种情况下,另一个线程是调用方,因此是问题所在。 您可能已经注意到,我希望使用相同的事件循环能够解决该问题。 las,那行不通。

我真的想保持界面完全不变,否则我将在今年余下的时间里进行重构。我可以相对容易地将参数传递给Network类的构造函数。我尝试传递在主线程上创建的事件循环,但结果是相同的。

(请注意,这是与作者相反的问题:Call coroutine within Thread

1 个答案:

答案 0 :(得分:1)

从其他线程安排协程时,必须使用asyncio.run_coroutine_threadsafe。例如:

    def fetch_something(self):
        future = asyncio.run_coroutine_threadsafe(
            self._client.fetch_something(), loop)
        return future.result()

run_coroutine_threadsafe以线程安全的方式调度带有事件循环的协程,并返回concurrent.futures.Future。您可以使用返回的Future来简单地等待结果,如上所示,但是您也可以将其传递给其他函数,查询结果是否已到达或实现超时。

在组合线程和异步时,请记住确保 all 与其他线程的事件循环接口(甚至调用loop.stop这样的东西来实现Network.stop )是使用loop.call_soon_threadsafeasyncio.run_coroutine_threadsafe完成的。