桌面應用啟動速度優化

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
"""
import asyncio
import os
import sys
import time
from typing import Optional
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:
"""
啟動桌面應用收集回饋
啟動桌面應用收集回饋優化版本支援並行啟動
Args:
project_dir: 專案目錄路徑
@ -74,61 +76,120 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
Returns:
dict: 收集到的回饋資料
"""
debug_log("啟動桌面應用...")
debug_log("啟動桌面應用(並行優化版本)...")
start_time = time.time()
try:
# 創建 Electron 管理器
from .electron_manager import ElectronManager
# 並行任務1創建 Electron 管理器和依賴檢查
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
from ..web import get_web_ui_manager
# 並行任務2初始化 Web 管理器和會話
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)
session = web_manager.get_current_session()
# 確保異步初始化完成
if hasattr(web_manager, "_init_async_components"):
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 web_manager.server_thread or not web_manager.server_thread.is_alive():
debug_log("啟動 Web 服務器...")
web_manager.start_server()
if not session:
raise RuntimeError("無法創建回饋會話")
# 等待 Web 服務器完全啟動
import time
return web_manager, session
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():
debug_log(f"✅ Web 服務器成功啟動在端口: {web_manager.port}")
# 檢查初始化結果
if isinstance(init_results[0], Exception):
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:
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 服務器端口
manager.set_web_server_port(web_manager.port)
debug_log(f"桌面應用將連接到: http://localhost:{web_manager.port}")
manager.set_web_server_port(server_port) # type: ignore[union-attr]
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:
debug_log("桌面應用啟動成功,等待用戶回饋...")
total_startup_time = time.time() - start_time
debug_log(
f"桌面應用啟動成功,總耗時: {total_startup_time:.2f}秒,等待用戶回饋..."
)
try:
# 等待用戶回饋
result = await session.wait_for_feedback(timeout)
debug_log("收到桌面應用用戶回饋")
return result
return result # type: ignore[no-any-return]
finally:
# 確保 Electron 進程被正確清理
debug_log("清理 Electron 進程...")
await manager.cleanup_async()
if not isinstance(manager, Exception):
await manager.cleanup_async() # type: ignore[union-attr]
debug_log("Electron 進程清理完成")
else:
debug_log("桌面應用啟動失敗,回退到 Web 模式")
@ -142,8 +203,8 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
debug_log("回退到 Web 模式")
# 確保清理 Electron 進程
try:
if "manager" in locals():
await manager.cleanup_async()
if "manager" in locals() and not isinstance(manager, Exception):
await manager.cleanup_async() # type: ignore[union-attr]
except Exception as 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)
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:
"""Electron 管理器 - 預留接口"""

View File

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

View File

@ -7,6 +7,7 @@ Web UI 主要管理類
"""
import asyncio
import concurrent.futures
import os
import threading
import time
@ -96,25 +97,64 @@ class WebUIManager:
self.server_thread: threading.Thread | None = 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.desktop_manager: Any = None
# 如果是桌面模式,嘗試初始化桌面管理器
if self.mode == "desktop":
self._init_desktop_manager()
# 設置靜態文件和模板
# 設置靜態文件和模板(必須同步)
self._setup_static_files()
self._setup_templates()
# 設置路由
# 設置路由(必須同步)
setup_routes(self)
debug_log(f"WebUIManager 初始化完成,將在 {self.host}:{self.port} 啟動")
debug_log(f"回饋模式: {self.mode}")
async def _init_async_components(self):
"""異步初始化組件(並行執行)"""
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:
"""檢測回饋模式"""
@ -125,7 +165,7 @@ class WebUIManager:
return "auto"
def _init_desktop_manager(self):
"""初始化桌面管理器(如果可用)"""
"""初始化桌面管理器(如果可用)- 同步版本"""
try:
# 嘗試導入桌面模組
from ..desktop import ElectronManager
@ -139,6 +179,46 @@ class WebUIManager:
debug_log(f"桌面管理器初始化失敗: {e}")
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:
"""判斷是否應該使用桌面模式"""
if self.mode == "web":
@ -415,7 +495,7 @@ class WebUIManager:
debug_log(f"廣播消息失敗: {e}")
def start_server(self):
"""啟動 Web 伺服器"""
"""啟動 Web 伺服器(優化版本,支援並行初始化)"""
def run_server_with_retry():
max_retries = 5
@ -435,8 +515,20 @@ class WebUIManager:
access_log=False,
)
server = uvicorn.Server(config)
asyncio.run(server.serve())
server_instance = uvicorn.Server(config)
# 創建事件循環並啟動服務器
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
except OSError as e: