From 1db3445e86d54739d711e44eef05c786b2ac04e8 Mon Sep 17 00:00:00 2001 From: wenchun Date: Thu, 13 Aug 2026 14:47:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(GUI):=20=E7=BE=A4=E7=B5=84=E5=88=87mod?= =?UTF-8?q?e/=E5=A5=97=E7=94=A8=E9=80=9F=E5=BA=A6=E6=94=B9=E4=B8=A6?= =?UTF-8?q?=E7=99=BC(asyncio.gather)=EF=BC=8C=E6=B8=85per-drone=20client?= =?UTF-8?q?=E9=81=8E=E6=9C=9F=E8=A8=BB=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gui.py: _handle_group_mode_change 與 _on_engagement_apply_speed 由 單一 async 函式內 for 迴圈逐台 await(序列化,最壞 N×timeout)改為 asyncio.gather 並發,各自等各自 ACK;行為語意不變。 - communication.py: set_mode/arm_drone/set_speed 前 3 處過期註解 「避免多機並行時的競態條件」更正為共用 client + target_sysid 路由 (OOM 修復後已非 per-drone、無 per-drone 狀態)。 Co-Authored-By: Claude Opus 4.8 --- src/GUI/communication.py | 9 ++++-- src/GUI/gui.py | 62 +++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/GUI/communication.py b/src/GUI/communication.py index b6c2e88..83d65f3 100644 --- a/src/GUI/communication.py +++ b/src/GUI/communication.py @@ -1163,7 +1163,8 @@ class DroneMonitor(Node): _log("INFO", f"[SET_MODE] {drone_id} -> {mode_name} (custom_mode={custom_mode})") - # 獲取或創建該 drone 專用的 client(避免多機並行時的競態條件) + # 取得共用 command_long client(OOM 修復後已非 per-drone,靠 request 的 + # target_sysid 路由;drone_id 僅用於一次性 lazy fallback,無 per-drone 狀態) client = self.get_or_create_client(drone_id) if not client: _log("ERROR", "[SET_MODE] CommandLongClient 無法初始化") @@ -1246,7 +1247,8 @@ class DroneMonitor(Node): action_name = "解鎖" if arm else "上鎖" _log("INFO", f"[ARM] {drone_id} -> {action_name}") - # 獲取或創建該 drone 專用的 client(避免多機並行時的競態條件) + # 取得共用 command_long client(OOM 修復後已非 per-drone,靠 request 的 + # target_sysid 路由;drone_id 僅用於一次性 lazy fallback,無 per-drone 狀態) client = self.get_or_create_client(drone_id) if not client: _log("ERROR", "[ARM] CommandLongClient 無法初始化") @@ -1283,7 +1285,8 @@ class DroneMonitor(Node): _log("INFO", f"[TAKEOFF] {drone_id} -> 起飛 (高度={altitude}m)") - # 獲取或創建該 drone 專用的 client(避免多機並行時的競態條件) + # 取得共用 command_long client(OOM 修復後已非 per-drone,靠 request 的 + # target_sysid 路由;drone_id 僅用於一次性 lazy fallback,無 per-drone 狀態) client = self.get_or_create_client(drone_id) if not client: _log("ERROR", "[TAKEOFF] CommandLongClient 無法初始化") diff --git a/src/GUI/gui.py b/src/GUI/gui.py index 17d177d..1a5896e 100644 --- a/src/GUI/gui.py +++ b/src/GUI/gui.py @@ -1573,27 +1573,30 @@ class ControlStationUI(QMainWindow): _log("INFO", f"Group {group_id} 準備切換模式為 {mode}") self.statusBar().showMessage("正在切換模式", 1000) - # 使用 asyncio 執行(通過事件循環) + # 使用 asyncio 執行(通過事件循環)。 + # 並發:每台各自 set_mode + 各自等自己的 ACK,用 gather 同時發, + # 避免逐台 await 造成序列化(最壞情況 N×timeout 一路卡著)。 + async def _change_one(drone_id): + try: + result = await self.monitor.set_mode(drone_id, mode) + if result: + msg = f"{drone_id} 切換成功" + _log("INFO", msg) + else: + msg = f"{drone_id} 切換失敗" + _log("WARN", msg) + self.message_queue.put((msg, 2000)) + except Exception as e: + msg = f"{drone_id} 錯誤: {str(e)}" + _log("ERROR", msg) + traceback.print_exc() + self.message_queue.put((msg, 2000)) + async def do_mode_changes_async(): - for drone_id in group.selected_drone_ids: - try: - result = await self.monitor.set_mode(drone_id, mode) - - if result: - msg = f"{drone_id} 切換成功" - _log("INFO", msg) - self.message_queue.put((msg, 2000)) - else: - msg = f"{drone_id} 切換失敗" - _log("WARN", msg) - self.message_queue.put((msg, 2000)) - - except Exception as e: - msg = f"{drone_id} 錯誤: {str(e)}" - _log("ERROR", msg) - traceback.print_exc() - self.message_queue.put((msg, 2000)) - + await asyncio.gather( + *(_change_one(did) for did in group.selected_drone_ids) + ) + # 通過事件循環提交異步任務 loop = asyncio.get_event_loop() asyncio.run_coroutine_threadsafe( @@ -2584,17 +2587,18 @@ class ControlStationUI(QMainWindow): if hasattr(self, 'orchestrator'): self.orchestrator.friendly_speed = float(friendly_speed) + async def _apply_one(did, speed, label): + try: + await self.monitor.set_speed(did, speed) + except Exception as e: + _log("ERROR", f"[套用速度] {label} {did} 失敗: {e}") + async def do_apply(): - for did in friendly_ids: - try: - await self.monitor.set_speed(did, friendly_speed) - except Exception as e: - _log("ERROR", f"[套用速度] 友機 {did} 失敗: {e}") + # 並發:友機各自 + 敵機,用 gather 同時發,各自等各自 ACK + tasks = [_apply_one(did, friendly_speed, "友機") for did in friendly_ids] if enemy_id: - try: - await self.monitor.set_speed(enemy_id, enemy_speed) - except Exception as e: - _log("ERROR", f"[套用速度] 敵機 {enemy_id} 失敗: {e}") + tasks.append(_apply_one(enemy_id, enemy_speed, "敵機")) + await asyncio.gather(*tasks) asyncio.run_coroutine_threadsafe(do_apply(), asyncio.get_event_loop()) self.statusBar().showMessage(