桌面應用啟動速度優化

This commit is contained in:
Minidoracat 2025-06-11 09:56:47 +08:00
parent d5ba76e248
commit 85ff83f037
3 changed files with 239 additions and 47 deletions

View File

@ -15,8 +15,10 @@
版本: 2.3.0 版本: 2.3.0
""" """
import asyncio
import os import os
import sys import sys
import time
from typing import Optional from typing import Optional
from ..debug import web_debug_log as debug_log from ..debug import web_debug_log as debug_log
@ -64,7 +66,7 @@ def is_desktop_available() -> bool:
async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> dict: async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> dict:
""" """
啟動桌面應用收集回饋 啟動桌面應用收集回饋優化版本支援並行啟動
Args: Args:
project_dir: 專案目錄路徑 project_dir: 專案目錄路徑
@ -74,61 +76,120 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
Returns: Returns:
dict: 收集到的回饋資料 dict: 收集到的回饋資料
""" """
debug_log("啟動桌面應用...") debug_log("啟動桌面應用(並行優化版本)...")
start_time = time.time()
try: try:
# 創建 Electron 管理器 # 並行任務1創建 Electron 管理器和依賴檢查
from .electron_manager import ElectronManager async def init_electron_manager():
from .electron_manager import ElectronManager
manager = ElectronManager() manager = ElectronManager()
# 預先檢查依賴(如果實現了異步版本)
if hasattr(manager, "ensure_dependencies_async"):
await manager.ensure_dependencies_async()
return manager
# 首先啟動 Web 服務器(桌面應用需要載入 Web UI # 並行任務2初始化 Web 管理器和會話
from ..web import get_web_ui_manager async def init_web_manager():
from ..web import get_web_ui_manager
web_manager = get_web_ui_manager() web_manager = get_web_ui_manager()
# 創建會話 # 確保異步初始化完成
web_manager.create_session(project_dir, summary) if hasattr(web_manager, "_init_async_components"):
session = web_manager.get_current_session() await web_manager._init_async_components()
if not session: # 創建會話
raise RuntimeError("無法創建回饋會話") web_manager.create_session(project_dir, summary)
session = web_manager.get_current_session()
# 啟動 Web 服務器(如果尚未啟動) if not session:
if not web_manager.server_thread or not web_manager.server_thread.is_alive(): raise RuntimeError("無法創建回饋會話")
debug_log("啟動 Web 服務器...")
web_manager.start_server()
# 等待 Web 服務器完全啟動 return web_manager, session
import time
debug_log("等待 Web 服務器啟動...") # 並行執行初始化任務
time.sleep(5) # 增加等待時間 debug_log("並行執行初始化任務...")
init_results = await asyncio.gather(
init_electron_manager(), init_web_manager(), return_exceptions=True
)
# 驗證 Web 服務器是否正常運行 # 檢查初始化結果
if web_manager.server_thread and web_manager.server_thread.is_alive(): if isinstance(init_results[0], Exception):
debug_log(f"✅ Web 服務器成功啟動在端口: {web_manager.port}") raise init_results[0]
if isinstance(init_results[1], Exception):
raise init_results[1]
manager = init_results[0]
web_result = init_results[1]
if isinstance(web_result, tuple) and len(web_result) == 2:
web_manager, session = web_result
else: else:
raise RuntimeError("Web 服務器啟動失敗") raise RuntimeError("Web 管理器初始化返回格式錯誤")
init_time = time.time() - start_time
debug_log(f"並行初始化完成,耗時: {init_time:.2f}")
# 並行任務3啟動 Web 服務器
async def start_web_server():
if (
not web_manager.server_thread
or not web_manager.server_thread.is_alive()
):
debug_log("啟動 Web 服務器...")
web_manager.start_server()
# 智能等待服務器啟動(減少固定等待時間)
await _wait_for_server_startup(web_manager)
return web_manager.port
# 並行任務4準備 Electron 環境
async def prepare_electron():
# 如果有預熱方法,在這裡調用
if hasattr(manager, "preheat_async"):
await manager.preheat_async()
return True
# 並行執行服務器啟動和 Electron 準備
debug_log("並行啟動 Web 服務器和準備 Electron 環境...")
startup_results = await asyncio.gather(
start_web_server(), prepare_electron(), return_exceptions=True
)
if isinstance(startup_results[0], Exception):
raise startup_results[0]
if isinstance(startup_results[1], Exception):
debug_log(f"Electron 預熱失敗(非致命): {startup_results[1]}")
server_port = startup_results[0]
# 類型檢查:確保 manager 不是 Exception
if isinstance(manager, Exception):
raise manager
# 設置 Web 服務器端口 # 設置 Web 服務器端口
manager.set_web_server_port(web_manager.port) manager.set_web_server_port(server_port) # type: ignore[union-attr]
debug_log(f"桌面應用將連接到: http://localhost:{web_manager.port}") debug_log(f"桌面應用將連接到: http://localhost:{server_port}")
# 啟動桌面應用 # 啟動桌面應用
desktop_success = await manager.launch_desktop_app(summary, project_dir) desktop_success = await manager.launch_desktop_app(summary, project_dir) # type: ignore[union-attr]
if desktop_success: if desktop_success:
debug_log("桌面應用啟動成功,等待用戶回饋...") total_startup_time = time.time() - start_time
debug_log(
f"桌面應用啟動成功,總耗時: {total_startup_time:.2f}秒,等待用戶回饋..."
)
try: try:
# 等待用戶回饋 # 等待用戶回饋
result = await session.wait_for_feedback(timeout) result = await session.wait_for_feedback(timeout)
debug_log("收到桌面應用用戶回饋") debug_log("收到桌面應用用戶回饋")
return result return result # type: ignore[no-any-return]
finally: finally:
# 確保 Electron 進程被正確清理 # 確保 Electron 進程被正確清理
debug_log("清理 Electron 進程...") debug_log("清理 Electron 進程...")
await manager.cleanup_async() if not isinstance(manager, Exception):
await manager.cleanup_async() # type: ignore[union-attr]
debug_log("Electron 進程清理完成") debug_log("Electron 進程清理完成")
else: else:
debug_log("桌面應用啟動失敗,回退到 Web 模式") debug_log("桌面應用啟動失敗,回退到 Web 模式")
@ -142,8 +203,8 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
debug_log("回退到 Web 模式") debug_log("回退到 Web 模式")
# 確保清理 Electron 進程 # 確保清理 Electron 進程
try: try:
if "manager" in locals(): if "manager" in locals() and not isinstance(manager, Exception):
await manager.cleanup_async() await manager.cleanup_async() # type: ignore[union-attr]
except Exception as cleanup_error: except Exception as cleanup_error:
debug_log(f"清理 Electron 進程時出錯: {cleanup_error}") debug_log(f"清理 Electron 進程時出錯: {cleanup_error}")
@ -153,6 +214,41 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
return await launch_web_feedback_ui(project_dir, summary, timeout) return await launch_web_feedback_ui(project_dir, summary, timeout)
async def _wait_for_server_startup(web_manager, max_wait: int = 10) -> bool:
"""智能等待服務器啟動"""
debug_log("智能等待 Web 服務器啟動...")
for attempt in range(max_wait):
if web_manager.server_thread and web_manager.server_thread.is_alive():
# 嘗試連接測試
try:
import aiohttp
timeout = aiohttp.ClientTimeout(total=1)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(
f"http://{web_manager.host}:{web_manager.port}/"
) as response:
if response.status == 200:
debug_log(
f"✅ Web 服務器啟動驗證成功,耗時: {attempt + 1}"
)
return True
except Exception:
pass
await asyncio.sleep(1)
debug_log(f"等待 Web 服務器啟動... ({attempt + 1}/{max_wait})")
# 回退到線程檢查
if web_manager.server_thread and web_manager.server_thread.is_alive():
debug_log("✅ Web 服務器線程運行正常(回退驗證)")
return True
debug_log("❌ Web 服務器啟動失敗")
return False
class ElectronManager: class ElectronManager:
"""Electron 管理器 - 預留接口""" """Electron 管理器 - 預留接口"""

View File

@ -163,6 +163,10 @@ class ElectronManager:
debug_log(f"依賴檢查失敗 [錯誤ID: {error_id}]: {e}") debug_log(f"依賴檢查失敗 [錯誤ID: {error_id}]: {e}")
return False return False
async def ensure_dependencies_async(self) -> bool:
"""異步確保依賴已安裝(別名方法)"""
return await self.ensure_dependencies()
def cleanup(self): def cleanup(self):
"""同步清理資源(向後兼容)""" """同步清理資源(向後兼容)"""
try: try:

View File

@ -7,6 +7,7 @@ Web UI 主要管理類
""" """
import asyncio import asyncio
import concurrent.futures
import os import os
import threading import threading
import time import time
@ -96,25 +97,64 @@ class WebUIManager:
self.server_thread: threading.Thread | None = None self.server_thread: threading.Thread | None = None
self.server_process = None self.server_process = None
self.i18n = get_i18n_manager()
# 添加模式檢測支援 # 初始化標記,用於追蹤異步初始化狀態
self._initialization_complete = False
self._initialization_lock = threading.Lock()
# 同步初始化基本組件
self._init_basic_components()
debug_log(f"WebUIManager 基本初始化完成,將在 {self.host}:{self.port} 啟動")
debug_log(f"回饋模式: {self.mode}")
def _init_basic_components(self):
"""同步初始化基本組件"""
# 基本組件初始化(必須同步)
self.i18n = get_i18n_manager()
self.mode = self._detect_feedback_mode() self.mode = self._detect_feedback_mode()
self.desktop_manager: Any = None self.desktop_manager: Any = None
# 如果是桌面模式,嘗試初始化桌面管理器 # 設置靜態文件和模板(必須同步)
if self.mode == "desktop":
self._init_desktop_manager()
# 設置靜態文件和模板
self._setup_static_files() self._setup_static_files()
self._setup_templates() self._setup_templates()
# 設置路由 # 設置路由(必須同步)
setup_routes(self) setup_routes(self)
debug_log(f"WebUIManager 初始化完成,將在 {self.host}:{self.port} 啟動") async def _init_async_components(self):
debug_log(f"回饋模式: {self.mode}") """異步初始化組件(並行執行)"""
with self._initialization_lock:
if self._initialization_complete:
return
debug_log("開始並行初始化組件...")
start_time = time.time()
# 創建並行任務
tasks = []
# 任務1桌面管理器初始化
if self.mode == "desktop":
tasks.append(self._init_desktop_manager_async())
# 任務2I18N 預載入(如果需要)
tasks.append(self._preload_i18n_async())
# 並行執行所有任務
if tasks:
results = await asyncio.gather(*tasks, return_exceptions=True)
# 檢查結果
for i, result in enumerate(results):
if isinstance(result, Exception):
debug_log(f"並行初始化任務 {i} 失敗: {result}")
with self._initialization_lock:
self._initialization_complete = True
elapsed = time.time() - start_time
debug_log(f"並行初始化完成,耗時: {elapsed:.2f}")
def _detect_feedback_mode(self) -> str: def _detect_feedback_mode(self) -> str:
"""檢測回饋模式""" """檢測回饋模式"""
@ -125,7 +165,7 @@ class WebUIManager:
return "auto" return "auto"
def _init_desktop_manager(self): def _init_desktop_manager(self):
"""初始化桌面管理器(如果可用)""" """初始化桌面管理器(如果可用)- 同步版本"""
try: try:
# 嘗試導入桌面模組 # 嘗試導入桌面模組
from ..desktop import ElectronManager from ..desktop import ElectronManager
@ -139,6 +179,46 @@ class WebUIManager:
debug_log(f"桌面管理器初始化失敗: {e}") debug_log(f"桌面管理器初始化失敗: {e}")
self.desktop_manager = None self.desktop_manager = None
async def _init_desktop_manager_async(self):
"""異步初始化桌面管理器"""
def init_desktop():
try:
from ..desktop import ElectronManager
manager = ElectronManager()
debug_log("桌面管理器異步初始化成功")
return manager
except ImportError:
debug_log("桌面模組不可用,將在需要時回退到 Web 模式")
return None
except Exception as e:
debug_log(f"桌面管理器異步初始化失敗: {e}")
return None
# 在線程池中執行同步初始化
loop = asyncio.get_event_loop()
with concurrent.futures.ThreadPoolExecutor() as executor:
self.desktop_manager = await loop.run_in_executor(executor, init_desktop)
async def _preload_i18n_async(self):
"""異步預載入 I18N 資源"""
def preload_i18n():
try:
# 觸發翻譯載入(如果尚未載入)
self.i18n.get_supported_languages()
debug_log("I18N 資源預載入完成")
return True
except Exception as e:
debug_log(f"I18N 資源預載入失敗: {e}")
return False
# 在線程池中執行
loop = asyncio.get_event_loop()
with concurrent.futures.ThreadPoolExecutor() as executor:
await loop.run_in_executor(executor, preload_i18n)
def should_use_desktop_mode(self) -> bool: def should_use_desktop_mode(self) -> bool:
"""判斷是否應該使用桌面模式""" """判斷是否應該使用桌面模式"""
if self.mode == "web": if self.mode == "web":
@ -415,7 +495,7 @@ class WebUIManager:
debug_log(f"廣播消息失敗: {e}") debug_log(f"廣播消息失敗: {e}")
def start_server(self): def start_server(self):
"""啟動 Web 伺服器""" """啟動 Web 伺服器(優化版本,支援並行初始化)"""
def run_server_with_retry(): def run_server_with_retry():
max_retries = 5 max_retries = 5
@ -435,8 +515,20 @@ class WebUIManager:
access_log=False, access_log=False,
) )
server = uvicorn.Server(config) server_instance = uvicorn.Server(config)
asyncio.run(server.serve())
# 創建事件循環並啟動服務器
async def serve_with_async_init(server=server_instance):
# 在服務器啟動的同時進行異步初始化
server_task = asyncio.create_task(server.serve())
init_task = asyncio.create_task(self._init_async_components())
# 等待兩個任務完成
await asyncio.gather(
server_task, init_task, return_exceptions=True
)
asyncio.run(serve_with_async_init())
break break
except OSError as e: except OSError as e: