코워크·코드 실행 중 UI 멈춤 체감 완화
에이전트 이벤트를 UI 스레드에서 과하게 즉시 처리하던 경로를 정리해 코워크·코드 실행 중 입력과 렌더가 먼저 흐르도록 조정했다. - ChatWindow agent dispatcher 우선순위를 Background로 낮춰 이벤트 폭주 시 UI 응답성을 확보 - OnAgentEvent 즉시 갱신을 완료/오류 중심으로 축소하고 나머지는 배치 타이머 경로로 이관 - ChatWindow.AgentEventProcessor에서 execution event 중복 append를 제거해 대화 히스토리 반영을 백그라운드 1회 처리로 단순화 - README.md, docs/DEVELOPMENT.md에 2026-04-10 09:03 (KST) 기준 작업 이력 반영 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ / 경고 0, 오류 0
This commit is contained in:
@@ -8,12 +8,12 @@ using AxCopilot.Services.Agent;
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
/// <summary>
|
||||
/// 에이전트 이벤트의 무거운 작업(대화 변이·저장)을 백그라운드 스레드에서 처리합니다.
|
||||
/// UI 스레드는 시각적 피드백만 담당하여 채팅창 멈춤을 방지합니다.
|
||||
/// 에이전트 이벤트의 무거운 작업(대화 반영, 저장, 실행 이력 기록)을
|
||||
/// 백그라운드 단일 리더에서 배치 처리해 UI 스레드 점유를 줄입니다.
|
||||
/// </summary>
|
||||
public partial class ChatWindow
|
||||
{
|
||||
/// <summary>백그라운드 처리 대상 이벤트 큐 아이템.</summary>
|
||||
/// <summary>백그라운드 처리 대상 에이전트 이벤트 단위입니다.</summary>
|
||||
private readonly record struct AgentEventWorkItem(
|
||||
AgentEvent Event,
|
||||
string EventTab,
|
||||
@@ -26,50 +26,31 @@ public partial class ChatWindow
|
||||
|
||||
private Task? _agentEventProcessorTask;
|
||||
|
||||
/// <summary>백그라운드 이벤트 프로세서를 시작합니다. 창 초기화 시 한 번 호출됩니다.</summary>
|
||||
/// <summary>백그라운드 이벤트 프로세서를 시작합니다.</summary>
|
||||
private void StartAgentEventProcessor()
|
||||
{
|
||||
_agentEventProcessorTask = Task.Run(ProcessAgentEventsAsync);
|
||||
}
|
||||
|
||||
/// <summary>백그라운드 이벤트 프로세서를 종료합니다. 창 닫기 시 호출됩니다.</summary>
|
||||
/// <summary>백그라운드 이벤트 프로세서를 종료합니다.</summary>
|
||||
private void StopAgentEventProcessor()
|
||||
{
|
||||
_agentEventChannel.Writer.TryComplete();
|
||||
// 프로세서 완료를 동기 대기하지 않음 — 데드락 방지
|
||||
// GC가 나머지를 정리합니다.
|
||||
// 종료 대기는 생략한다. 남은 정리는 GC와 종료 루틴에 맡긴다.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 에이전트 이벤트를 백그라운드 큐에 추가합니다.
|
||||
/// ExecutionEvents는 UI 스레드에서 즉시 추가 (타임라인 렌더링 누락 방지).
|
||||
/// 디스크 저장과 AgentRun 기록은 백그라운드에서 처리합니다.
|
||||
/// 대화 히스토리 반영과 저장은 프로세서에서 한 번만 수행합니다.
|
||||
/// </summary>
|
||||
private void EnqueueAgentEventWork(AgentEvent evt, string eventTab, bool shouldRender)
|
||||
{
|
||||
// ── 즉시 추가: ExecutionEvents에 동기적으로 반영 (RenderMessages 누락 방지) ──
|
||||
try
|
||||
{
|
||||
lock (_convLock)
|
||||
{
|
||||
var session = _appState.ChatSession;
|
||||
if (session != null)
|
||||
{
|
||||
var result = _chatEngine.AppendExecutionEvent(
|
||||
session, null!, _currentConversation, _activeTab, eventTab, evt);
|
||||
_currentConversation = result.CurrentConversation;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Debug($"UI 스레드 이벤트 즉시 추가 실패: {ex.Message}");
|
||||
}
|
||||
|
||||
_agentEventChannel.Writer.TryWrite(new AgentEventWorkItem(evt, eventTab, _activeTab, shouldRender));
|
||||
}
|
||||
|
||||
/// <summary>백그라운드 전용: 채널에서 이벤트를 배치로 읽어 대화 변이 + 저장을 수행합니다.</summary>
|
||||
/// <summary>
|
||||
/// 백그라운드 전용: 채널에 쌓인 이벤트를 배치로 읽어 대화 반영과 저장을 수행합니다.
|
||||
/// </summary>
|
||||
private async Task ProcessAgentEventsAsync()
|
||||
{
|
||||
var reader = _agentEventChannel.Reader;
|
||||
@@ -97,7 +78,6 @@ public partial class ChatWindow
|
||||
var eventTab = work.EventTab;
|
||||
var activeTab = work.ActiveTab;
|
||||
|
||||
// ── 대화 변이: execution event 추가 ──
|
||||
try
|
||||
{
|
||||
lock (_convLock)
|
||||
@@ -117,7 +97,6 @@ public partial class ChatWindow
|
||||
LogService.Debug($"백그라운드 이벤트 처리 오류 (execution): {ex.Message}");
|
||||
}
|
||||
|
||||
// ── 대화 변이: agent run 추가 (Complete/Error) ──
|
||||
if (evt.Type == AgentEventType.Complete)
|
||||
{
|
||||
try
|
||||
@@ -139,6 +118,7 @@ public partial class ChatWindow
|
||||
{
|
||||
LogService.Debug($"백그라운드 이벤트 처리 오류 (agent run complete): {ex.Message}");
|
||||
}
|
||||
|
||||
hasTerminalEvent = true;
|
||||
}
|
||||
else if (evt.Type == AgentEventType.Error && string.IsNullOrWhiteSpace(evt.ToolName))
|
||||
@@ -162,6 +142,7 @@ public partial class ChatWindow
|
||||
{
|
||||
LogService.Debug($"백그라운드 이벤트 처리 오류 (agent run error): {ex.Message}");
|
||||
}
|
||||
|
||||
hasTerminalEvent = true;
|
||||
}
|
||||
|
||||
@@ -169,7 +150,6 @@ public partial class ChatWindow
|
||||
anyNeedsRender = true;
|
||||
}
|
||||
|
||||
// ── 디바운스 저장: 2초마다 또는 종료 이벤트 시 즉시 ──
|
||||
if (pendingPersist != null && (hasTerminalEvent || persistStopwatch.ElapsedMilliseconds > 2000))
|
||||
{
|
||||
try
|
||||
@@ -182,11 +162,11 @@ public partial class ChatWindow
|
||||
{
|
||||
LogService.Debug($"백그라운드 대화 저장 실패: {ex.Message}");
|
||||
}
|
||||
|
||||
pendingPersist = null;
|
||||
persistStopwatch.Restart();
|
||||
}
|
||||
|
||||
// ── UI 새로고침 신호: 배치 전체에 대해 단 1회 ──
|
||||
if (anyNeedsRender)
|
||||
{
|
||||
try
|
||||
@@ -195,7 +175,10 @@ public partial class ChatWindow
|
||||
() => ScheduleExecutionHistoryRender(autoScroll: true),
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
catch { /* 앱 종료 중 무시 */ }
|
||||
catch
|
||||
{
|
||||
// 창 종료 중에는 무시한다.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,10 +189,16 @@ public partial class ChatWindow
|
||||
LogService.Debug($"에이전트 이벤트 프로세서 종료: {ex.Message}");
|
||||
}
|
||||
|
||||
// ── 종료 시 미저장 대화 플러시 ──
|
||||
if (pendingPersist != null)
|
||||
{
|
||||
try { _storage.Save(pendingPersist); } catch { }
|
||||
try
|
||||
{
|
||||
_storage.Save(pendingPersist);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 종료 중 마지막 저장 실패는 무시한다.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user