异步事件循环被阻塞的原因及解锁方案(要求仅修改sender方法)
我来帮你拆解问题根源,再给出符合要求的解决方案:
问题出在哪?
你的代码里藏了一个容易忽略的细节:Drone类初始化时,self.base_loop = asyncio.get_event_loop()获取的是默认事件循环,但在main函数里,你后来又创建了一个全新的事件循环loop = asyncio.new_event_loop(),还把它设为了main线程的当前循环。这就导致你存在Drone里的base_loop和实际运行run协程的循环根本不是同一个!
所以当你在sender线程用asyncio.run_coroutine_threadsafe(self.adjust_yaw(), self.base_loop)提交协程时,这个任务被送到了一个没有在运行的旧循环上,自然不会被执行。而run协程里的await asyncio.sleep(.2)其实并没有阻塞事件循环——它会主动让出控制权,只是你提交的任务没到正确的循环上而已。
仅修改sender方法的解决方案
如果你不想改动代码的其他部分,只修改sender方法的话,可以直接用asyncio.run来运行adjust_yaw协程。asyncio.run会自动创建一个临时事件循环来执行这个协程,虽然和运行run的循环不是同一个,但能保证adjust_yaw正常执行:
def sender(self): time.sleep(2) print("adjusting yaw angle...") asyncio.run(self.adjust_yaw())
运行修改后的代码,你就能看到Yaw angle adjusted.的输出了。
更规范的可选方案(需少量修改其他部分)
如果希望adjust_yaw和run在同一个事件循环里运行(比如需要共享状态、协作执行),那最优解是让Drone的base_loop指向实际运行的循环。只需要在main函数里创建新循环后,把它赋值给drone的base_loop:
if __name__ == '__main__': drone = Drone() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) # 新增:让drone的base_loop指向实际运行的循环 drone.base_loop = loop th = threading.Thread(target=drone.sender, name="MissionRunner", args=[]) th.start() loop.run_until_complete(drone.run())
这样原来的sender方法不用改,提交的协程就会被正确送到运行run的循环上执行。
验证结果
不管用哪种方案,运行代码后都会看到如下输出:
Mission was temporarily halted, starting waiting loop... adjusting yaw angle... Yaw angle adjusted.
备注:内容来源于stack exchange,提问作者user3792852

