理解协程、异步、事件循环
async def定义的函数在调用时都要加上await。程序运行到 await 时如果等待的操作尚未完成当前协程会暂停并把执行权交回同一个事件循环。事件循环可以调度其他就绪协程。当套接字收到数据、定时器到期、工作线程完成或其他等待条件满足时对应的 Future 会被标记为完成当前协程重新进入就绪队列随后从 await 所在位置继续执行。await 可以理解为 我需要这个结果如果还没好我先暂停好了以后通知事件循环事件循环再安排我继续。await 提供了“可以暂停”的机会不代表每次必然发生任务切换。不同的 await恢复条件不同await asyncio.sleep(0.05)——事件循环中的50毫秒定时器到期data await sock.recv_pyobj()——ZMQ套接字上有数据可以读取cur_vol await asyncio.to_thread(get_camera_param, self.camera_addr, r_param_id,)——工作线程中的 get_camera_param() 执行完毕常用方式defprocess(self):主进程入口# 设置进程标题ifHAS_SETPROCTITLE:setproctitle.setproctitle(self.mode)# 初始化ZMQ上下文self.zmqContextzmq.asyncio.Context()# 启动心跳发送子进程只发送心跳不写watchdogself.watchdog_loop(interval10)# 启动异步事件循环asyncio.run(self.start())asyncdefstart(self):启动控制服务tasks[self._control_service(name综控,entry_keycap_entry,handlerself.parse_cap,timeout2.0,)]ifself.mode!mwir_zoom:tasks.append(self._control_service(name相机控制,entry_keycam_entry,handlerself.parse_cam,timeout2.0,))# 建议 return_exceptionsFalse这样其中一个任务异常会直接抛出便于发现问题awaitasyncio.gather(*tasks,return_exceptionsFalse)await asyncio.gather并发启动两个 _control_service 循环两个服务同时运行、互不阻塞——综控服务在等待消息时相机控制服务可以正常收发为什么不用线程线程会带来更多复杂性• 线程创建和切换成本比协程高• 多个线程访问共享变量需要加锁• 容易出现竞争条件• 线程发生异常时管理更复杂• 大量等待型任务使用大量线程比较浪费。而 ZMQ 网络接收这种任务大部分时间都在“等数据”非常适合协程。本项目采用的是混合方式ZMQ网络收发asyncio协程短时间计算直接在事件循环执行阻塞V4L2访问asyncio.to_thread工作线程心跳调度独立后台线程每个相机模式独立进程这是工程中很常见的设计协程负责组织大量等待型任务线程负责兼容现有的同步阻塞接口。