mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 09:14:25 +08:00
feat.依旧试增量更新这一块,看看velopack
This commit is contained in:
380
LanMountainDesktop/Services/Loading/LoadingStateManager.cs
Normal file
380
LanMountainDesktop/Services/Loading/LoadingStateManager.cs
Normal file
@@ -0,0 +1,380 @@
|
||||
using System.Collections.Concurrent;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
|
||||
namespace LanMountainDesktop.Services.Loading;
|
||||
|
||||
/// <summary>
|
||||
/// 加载状态管理器 - 管理所有加载项的状态
|
||||
/// </summary>
|
||||
public class LoadingStateManager : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, LoadingItem> _items = new();
|
||||
private readonly ConcurrentDictionary<string, DateTimeOffset> _startTimes = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更事件
|
||||
/// </summary>
|
||||
public event EventHandler<LoadingStateChangedEventArgs>? StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 整体进度变更事件
|
||||
/// </summary>
|
||||
public event EventHandler<OverallProgressChangedEventArgs>? OverallProgressChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 当前启动阶段
|
||||
/// </summary>
|
||||
public StartupStage CurrentStage { get; private set; } = StartupStage.Initializing;
|
||||
|
||||
/// <summary>
|
||||
/// 整体进度百分比
|
||||
/// </summary>
|
||||
public int OverallProgressPercent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在加载
|
||||
/// </summary>
|
||||
public bool IsLoading => _items.Values.Any(i => i.State == LoadingState.InProgress);
|
||||
|
||||
/// <summary>
|
||||
/// 是否有错误
|
||||
/// </summary>
|
||||
public bool HasErrors => _items.Values.Any(i => i.State == LoadingState.Failed);
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有加载项
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<LoadingItem> GetAllItems() => _items.Values.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// 获取活动的加载项
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<LoadingItem> GetActiveItems() =>
|
||||
_items.Values.Where(i => i.State is LoadingState.InProgress or LoadingState.Pending).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// 注册加载项
|
||||
/// </summary>
|
||||
public LoadingItem RegisterItem(
|
||||
string id,
|
||||
LoadingItemType type,
|
||||
string name,
|
||||
string? description = null,
|
||||
Dictionary<string, string>? metadata = null)
|
||||
{
|
||||
var item = new LoadingItem
|
||||
{
|
||||
Id = id,
|
||||
Type = type,
|
||||
Name = name,
|
||||
Description = description,
|
||||
State = LoadingState.Pending,
|
||||
ProgressPercent = 0,
|
||||
Metadata = metadata,
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
_items[id] = item;
|
||||
|
||||
StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
|
||||
{
|
||||
Item = item,
|
||||
PreviousState = null,
|
||||
CurrentState = item.State
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始加载
|
||||
/// </summary>
|
||||
public void StartItem(string id, string? message = null)
|
||||
{
|
||||
if (!_items.TryGetValue(id, out var item))
|
||||
return;
|
||||
|
||||
var previousState = item.State;
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
|
||||
_startTimes[id] = startTime;
|
||||
|
||||
var updatedItem = item with
|
||||
{
|
||||
State = LoadingState.InProgress,
|
||||
StartTime = startTime,
|
||||
Message = message ?? $"正在加载 {item.Name}...",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
_items[id] = updatedItem;
|
||||
|
||||
StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
|
||||
{
|
||||
Item = updatedItem,
|
||||
PreviousState = previousState,
|
||||
CurrentState = updatedItem.State
|
||||
});
|
||||
|
||||
UpdateOverallProgress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新进度
|
||||
/// </summary>
|
||||
public void UpdateProgress(string id, int percent, string? message = null, int? estimatedRemainingSeconds = null)
|
||||
{
|
||||
if (!_items.TryGetValue(id, out var item))
|
||||
return;
|
||||
|
||||
var updatedItem = item with
|
||||
{
|
||||
ProgressPercent = Math.Clamp(percent, 0, 100),
|
||||
Message = message ?? item.Message,
|
||||
EstimatedRemainingSeconds = estimatedRemainingSeconds,
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
_items[id] = updatedItem;
|
||||
|
||||
StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
|
||||
{
|
||||
Item = updatedItem,
|
||||
PreviousState = item.State,
|
||||
CurrentState = updatedItem.State,
|
||||
IsProgressUpdate = true
|
||||
});
|
||||
|
||||
UpdateOverallProgress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完成加载
|
||||
/// </summary>
|
||||
public void CompleteItem(string id, string? message = null)
|
||||
{
|
||||
if (!_items.TryGetValue(id, out var item))
|
||||
return;
|
||||
|
||||
var previousState = item.State;
|
||||
var endTime = DateTimeOffset.UtcNow;
|
||||
|
||||
_startTimes.TryRemove(id, out _);
|
||||
|
||||
var updatedItem = item with
|
||||
{
|
||||
State = LoadingState.Completed,
|
||||
ProgressPercent = 100,
|
||||
EndTime = endTime,
|
||||
Message = message ?? $"{item.Name} 加载完成",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
_items[id] = updatedItem;
|
||||
|
||||
StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
|
||||
{
|
||||
Item = updatedItem,
|
||||
PreviousState = previousState,
|
||||
CurrentState = updatedItem.State
|
||||
});
|
||||
|
||||
UpdateOverallProgress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记失败
|
||||
/// </summary>
|
||||
public void FailItem(string id, string errorMessage, string? details = null)
|
||||
{
|
||||
if (!_items.TryGetValue(id, out var item))
|
||||
return;
|
||||
|
||||
var previousState = item.State;
|
||||
var endTime = DateTimeOffset.UtcNow;
|
||||
|
||||
_startTimes.TryRemove(id, out _);
|
||||
|
||||
var fullErrorMessage = string.IsNullOrEmpty(details)
|
||||
? errorMessage
|
||||
: $"{errorMessage}: {details}";
|
||||
|
||||
var updatedItem = item with
|
||||
{
|
||||
State = LoadingState.Failed,
|
||||
ErrorMessage = fullErrorMessage,
|
||||
EndTime = endTime,
|
||||
Message = $"{item.Name} 加载失败",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
_items[id] = updatedItem;
|
||||
|
||||
StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
|
||||
{
|
||||
Item = updatedItem,
|
||||
PreviousState = previousState,
|
||||
CurrentState = updatedItem.State
|
||||
});
|
||||
|
||||
UpdateOverallProgress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记超时
|
||||
/// </summary>
|
||||
public void TimeoutItem(string id, string? message = null)
|
||||
{
|
||||
if (!_items.TryGetValue(id, out var item))
|
||||
return;
|
||||
|
||||
var previousState = item.State;
|
||||
var endTime = DateTimeOffset.UtcNow;
|
||||
|
||||
_startTimes.TryRemove(id, out _);
|
||||
|
||||
var updatedItem = item with
|
||||
{
|
||||
State = LoadingState.Timeout,
|
||||
EndTime = endTime,
|
||||
Message = message ?? $"{item.Name} 加载超时",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
_items[id] = updatedItem;
|
||||
|
||||
StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
|
||||
{
|
||||
Item = updatedItem,
|
||||
PreviousState = previousState,
|
||||
CurrentState = updatedItem.State
|
||||
});
|
||||
|
||||
UpdateOverallProgress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前启动阶段
|
||||
/// </summary>
|
||||
public void SetStage(StartupStage stage, string? message = null)
|
||||
{
|
||||
CurrentStage = stage;
|
||||
|
||||
OverallProgressChanged?.Invoke(this, new OverallProgressChangedEventArgs
|
||||
{
|
||||
Stage = stage,
|
||||
OverallProgressPercent = OverallProgressPercent,
|
||||
Message = message
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新整体进度
|
||||
/// </summary>
|
||||
private void UpdateOverallProgress()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var items = _items.Values.ToList();
|
||||
if (items.Count == 0)
|
||||
{
|
||||
OverallProgressPercent = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算加权进度
|
||||
var totalWeight = items.Count;
|
||||
var completedWeight = items.Count(i => i.State == LoadingState.Completed);
|
||||
var inProgressWeight = items
|
||||
.Where(i => i.State == LoadingState.InProgress)
|
||||
.Sum(i => i.ProgressPercent / 100.0);
|
||||
|
||||
var progress = (int)((completedWeight + inProgressWeight) / totalWeight * 100);
|
||||
OverallProgressPercent = Math.Clamp(progress, 0, 100);
|
||||
|
||||
OverallProgressChanged?.Invoke(this, new OverallProgressChangedEventArgs
|
||||
{
|
||||
Stage = CurrentStage,
|
||||
OverallProgressPercent = OverallProgressPercent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取加载状态消息
|
||||
/// </summary>
|
||||
public LoadingStateMessage GetLoadingStateMessage()
|
||||
{
|
||||
var items = _items.Values.ToList();
|
||||
var activeItems = items.Where(i => i.State is LoadingState.InProgress or LoadingState.Pending).ToList();
|
||||
var errorItems = items.Where(i => i.State == LoadingState.Failed).ToList();
|
||||
|
||||
return new LoadingStateMessage
|
||||
{
|
||||
Stage = CurrentStage,
|
||||
OverallProgressPercent = OverallProgressPercent,
|
||||
ActiveItems = activeItems,
|
||||
CompletedCount = items.Count(i => i.State == LoadingState.Completed),
|
||||
TotalCount = items.Count,
|
||||
HasErrors = errorItems.Any(),
|
||||
ErrorMessages = errorItems.Select(i => $"{i.Name}: {i.ErrorMessage}").ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理所有加载项
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_items.Clear();
|
||||
_startTimes.Clear();
|
||||
OverallProgressPercent = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查超时项
|
||||
/// </summary>
|
||||
public void CheckTimeouts(TimeSpan timeout)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var timeoutItems = _items.Values
|
||||
.Where(i => i.State == LoadingState.InProgress && i.StartTime.HasValue)
|
||||
.Where(i => now - i.StartTime.Value > timeout)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in timeoutItems)
|
||||
{
|
||||
TimeoutItem(item.Id, $"{item.Name} 加载超时(超过 {timeout.TotalSeconds} 秒)");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_items.Clear();
|
||||
_startTimes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载状态变更事件参数
|
||||
/// </summary>
|
||||
public class LoadingStateChangedEventArgs : EventArgs
|
||||
{
|
||||
public required LoadingItem Item { get; init; }
|
||||
public LoadingState? PreviousState { get; init; }
|
||||
public required LoadingState CurrentState { get; init; }
|
||||
public bool IsProgressUpdate { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 整体进度变更事件参数
|
||||
/// </summary>
|
||||
public class OverallProgressChangedEventArgs : EventArgs
|
||||
{
|
||||
public StartupStage Stage { get; init; }
|
||||
public int OverallProgressPercent { get; init; }
|
||||
public string? Message { get; init; }
|
||||
}
|
||||
360
LanMountainDesktop/Services/Loading/LoadingStateReporter.cs
Normal file
360
LanMountainDesktop/Services/Loading/LoadingStateReporter.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using System.Timers;
|
||||
using LanMountainDesktop.Services.Launcher;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
|
||||
namespace LanMountainDesktop.Services.Loading;
|
||||
|
||||
/// <summary>
|
||||
/// 加载状态上报器 - 将加载状态实时上报给 Launcher
|
||||
/// </summary>
|
||||
public class LoadingStateReporter : IDisposable
|
||||
{
|
||||
private readonly LoadingStateManager _manager;
|
||||
private readonly LauncherIpcClient? _ipcClient;
|
||||
private readonly System.Timers.Timer _reportTimer;
|
||||
private readonly object _lock = new();
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// 上报间隔(毫秒)
|
||||
/// </summary>
|
||||
public int ReportIntervalMs { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用批量上报优化
|
||||
/// </summary>
|
||||
public bool EnableBatching { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 最小上报间隔(毫秒),用于限制高频更新
|
||||
/// </summary>
|
||||
public int MinReportIntervalMs { get; set; } = 50;
|
||||
|
||||
private DateTimeOffset _lastReportTime = DateTimeOffset.MinValue;
|
||||
private DetailedProgressMessage? _pendingMessage;
|
||||
private bool _hasPendingMessage;
|
||||
|
||||
public LoadingStateReporter(
|
||||
LoadingStateManager manager,
|
||||
LauncherIpcClient? ipcClient = null)
|
||||
{
|
||||
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
|
||||
_ipcClient = ipcClient;
|
||||
|
||||
// 创建定时上报定时器
|
||||
_reportTimer = new System.Timers.Timer(ReportIntervalMs);
|
||||
_reportTimer.Elapsed += OnReportTimerElapsed;
|
||||
_reportTimer.AutoReset = true;
|
||||
|
||||
// 订阅状态变更事件
|
||||
_manager.StateChanged += OnStateChanged;
|
||||
_manager.OverallProgressChanged += OnOverallProgressChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动上报
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
_reportTimer.Start();
|
||||
AppLogger.Info("LoadingStateReporter", "Loading state reporter started");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止上报
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_reportTimer.Stop();
|
||||
|
||||
// 发送任何待处理的消息
|
||||
FlushPendingMessage();
|
||||
|
||||
AppLogger.Info("LoadingStateReporter", "Loading state reporter stopped");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即上报当前状态
|
||||
/// </summary>
|
||||
public async Task ReportImmediatelyAsync()
|
||||
{
|
||||
if (_isDisposed || _ipcClient == null) return;
|
||||
|
||||
var message = CreateDetailedProgressMessage();
|
||||
await SendMessageAsync(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上报单个加载项的进度
|
||||
/// </summary>
|
||||
public async Task ReportItemProgressAsync(string itemId, int percent, string? message = null)
|
||||
{
|
||||
if (_isDisposed || _ipcClient == null) return;
|
||||
|
||||
var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
|
||||
if (item == null) return;
|
||||
|
||||
var updatedItem = item with
|
||||
{
|
||||
ProgressPercent = percent,
|
||||
Message = message ?? item.Message,
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
var progressMessage = new DetailedProgressMessage
|
||||
{
|
||||
Stage = _manager.CurrentStage,
|
||||
ProgressPercent = _manager.OverallProgressPercent,
|
||||
CurrentItem = updatedItem,
|
||||
AllItems = _manager.GetAllItems().ToList(),
|
||||
Message = message,
|
||||
IsMajorUpdate = false
|
||||
};
|
||||
|
||||
await SendMessageAsync(progressMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上报阶段变更
|
||||
/// </summary>
|
||||
public async Task ReportStageChangeAsync(StartupStage stage, string? message = null)
|
||||
{
|
||||
if (_isDisposed || _ipcClient == null) return;
|
||||
|
||||
var progressMessage = new DetailedProgressMessage
|
||||
{
|
||||
Stage = stage,
|
||||
ProgressPercent = _manager.OverallProgressPercent,
|
||||
AllItems = _manager.GetAllItems().ToList(),
|
||||
Message = message ?? $"进入阶段: {stage}",
|
||||
IsMajorUpdate = true
|
||||
};
|
||||
|
||||
await SendMessageAsync(progressMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上报错误
|
||||
/// </summary>
|
||||
public async Task ReportErrorAsync(string errorMessage, string? details = null)
|
||||
{
|
||||
if (_isDisposed || _ipcClient == null) return;
|
||||
|
||||
var fullMessage = string.IsNullOrEmpty(details)
|
||||
? errorMessage
|
||||
: $"{errorMessage}: {details}";
|
||||
|
||||
var progressMessage = new DetailedProgressMessage
|
||||
{
|
||||
Stage = _manager.CurrentStage,
|
||||
ProgressPercent = _manager.OverallProgressPercent,
|
||||
AllItems = _manager.GetAllItems().ToList(),
|
||||
Message = fullMessage,
|
||||
IsMajorUpdate = true
|
||||
};
|
||||
|
||||
await SendMessageAsync(progressMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更事件处理
|
||||
/// </summary>
|
||||
private void OnStateChanged(object? sender, LoadingStateChangedEventArgs e)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
// 重要状态变更立即上报
|
||||
if (e.CurrentState is LoadingState.Completed or LoadingState.Failed or LoadingState.Timeout)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ReportImmediatelyAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("LoadingStateReporter", $"Failed to report state change: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他状态变更标记为待处理
|
||||
QueueMessage(CreateDetailedProgressMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 整体进度变更事件处理
|
||||
/// </summary>
|
||||
private void OnOverallProgressChanged(object? sender, OverallProgressChangedEventArgs e)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
QueueMessage(CreateDetailedProgressMessage(e.Message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时上报处理
|
||||
/// </summary>
|
||||
private void OnReportTimerElapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
FlushPendingMessage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将消息加入待处理队列
|
||||
/// </summary>
|
||||
private void QueueMessage(DetailedProgressMessage message)
|
||||
{
|
||||
if (!EnableBatching)
|
||||
{
|
||||
// 如果不启用批量,立即发送
|
||||
_ = Task.Run(async () => await SendMessageAsync(message));
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_pendingMessage = message;
|
||||
_hasPendingMessage = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新待处理消息
|
||||
/// </summary>
|
||||
private void FlushPendingMessage()
|
||||
{
|
||||
DetailedProgressMessage? message;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_hasPendingMessage) return;
|
||||
|
||||
message = _pendingMessage;
|
||||
_pendingMessage = null;
|
||||
_hasPendingMessage = false;
|
||||
}
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendMessageAsync(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("LoadingStateReporter", $"Failed to flush pending message: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建详细的进度消息
|
||||
/// </summary>
|
||||
private DetailedProgressMessage CreateDetailedProgressMessage(string? message = null)
|
||||
{
|
||||
var activeItems = _manager.GetActiveItems().ToList();
|
||||
var currentItem = activeItems.FirstOrDefault();
|
||||
|
||||
return new DetailedProgressMessage
|
||||
{
|
||||
Stage = _manager.CurrentStage,
|
||||
ProgressPercent = _manager.OverallProgressPercent,
|
||||
CurrentItem = currentItem,
|
||||
AllItems = _manager.GetAllItems().ToList(),
|
||||
Message = message ?? currentItem?.Message,
|
||||
IsMajorUpdate = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
private async Task SendMessageAsync(DetailedProgressMessage message)
|
||||
{
|
||||
if (_ipcClient == null) return;
|
||||
|
||||
// 检查最小上报间隔
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var elapsed = now - _lastReportTime;
|
||||
if (elapsed.TotalMilliseconds < MinReportIntervalMs)
|
||||
{
|
||||
await Task.Delay(MinReportIntervalMs - (int)elapsed.TotalMilliseconds);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 转换为 StartupProgressMessage 以保持兼容性
|
||||
var baseMessage = new StartupProgressMessage
|
||||
{
|
||||
Stage = message.Stage,
|
||||
ProgressPercent = message.ProgressPercent,
|
||||
Message = FormatMessage(message),
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
await _ipcClient.ReportProgressAsync(baseMessage);
|
||||
_lastReportTime = DateTimeOffset.UtcNow;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("LoadingStateReporter", $"Failed to send message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化消息
|
||||
/// </summary>
|
||||
private string FormatMessage(DetailedProgressMessage message)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (message.CurrentItem != null)
|
||||
{
|
||||
parts.Add($"[{message.CurrentItem.Type}] {message.CurrentItem.Name}");
|
||||
|
||||
if (message.CurrentItem.ProgressPercent > 0)
|
||||
{
|
||||
parts.Add($"{message.CurrentItem.ProgressPercent}%");
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message.Message))
|
||||
{
|
||||
parts.Add(message.Message);
|
||||
}
|
||||
|
||||
var completedCount = message.AllItems?.Count(i => i.State == LoadingState.Completed) ?? 0;
|
||||
var totalCount = message.AllItems?.Count ?? 0;
|
||||
|
||||
if (totalCount > 0)
|
||||
{
|
||||
parts.Add($"({completedCount}/{totalCount})");
|
||||
}
|
||||
|
||||
return string.Join(" - ", parts);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
Stop();
|
||||
|
||||
_reportTimer.Elapsed -= OnReportTimerElapsed;
|
||||
_reportTimer.Dispose();
|
||||
|
||||
_manager.StateChanged -= OnStateChanged;
|
||||
_manager.OverallProgressChanged -= OnOverallProgressChanged;
|
||||
}
|
||||
}
|
||||
201
LanMountainDesktop/Services/Loading/LoadingStateUsageExample.cs
Normal file
201
LanMountainDesktop/Services/Loading/LoadingStateUsageExample.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
|
||||
namespace LanMountainDesktop.Services.Loading;
|
||||
|
||||
/// <summary>
|
||||
/// 加载状态管理使用示例
|
||||
/// </summary>
|
||||
public static class LoadingStateUsageExample
|
||||
{
|
||||
/// <summary>
|
||||
/// 示例:插件加载
|
||||
/// </summary>
|
||||
public static async Task LoadPluginsExample(LoadingStateManager manager)
|
||||
{
|
||||
// 注册插件加载项
|
||||
var pluginItem = manager.RegisterItem(
|
||||
"plugins.core",
|
||||
LoadingItemType.Plugin,
|
||||
"核心插件",
|
||||
"加载系统核心插件",
|
||||
new Dictionary<string, string> { { "version", "1.0.0" } });
|
||||
|
||||
// 开始加载
|
||||
manager.StartItem("plugins.core", "正在下载插件...");
|
||||
|
||||
try
|
||||
{
|
||||
// 模拟下载进度
|
||||
for (int i = 0; i <= 100; i += 10)
|
||||
{
|
||||
manager.UpdateProgress(
|
||||
"plugins.core",
|
||||
i,
|
||||
$"正在下载... {i}%",
|
||||
estimatedRemainingSeconds: (100 - i) / 10);
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
// 完成加载
|
||||
manager.CompleteItem("plugins.core", "核心插件加载完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 标记失败
|
||||
manager.FailItem("plugins.core", "插件加载失败", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:组件加载
|
||||
/// </summary>
|
||||
public static async Task LoadComponentsExample(LoadingStateManager manager)
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
("comp.weather", "天气组件"),
|
||||
("comp.clock", "时钟组件"),
|
||||
("comp.calendar", "日历组件")
|
||||
};
|
||||
|
||||
foreach (var (id, name) in components)
|
||||
{
|
||||
// 注册组件
|
||||
manager.RegisterItem(id, LoadingItemType.Component, name);
|
||||
|
||||
// 开始加载
|
||||
manager.StartItem(id, $"正在加载 {name}...");
|
||||
|
||||
// 模拟加载过程
|
||||
for (int i = 0; i <= 100; i += 20)
|
||||
{
|
||||
manager.UpdateProgress(id, i);
|
||||
await Task.Delay(50);
|
||||
}
|
||||
|
||||
// 完成
|
||||
manager.CompleteItem(id, $"{name} 加载完成");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:网络资源加载
|
||||
/// </summary>
|
||||
public static async Task LoadNetworkResourcesExample(LoadingStateManager manager)
|
||||
{
|
||||
// 注册网络加载项
|
||||
manager.RegisterItem(
|
||||
"network.config",
|
||||
LoadingItemType.Network,
|
||||
"配置数据",
|
||||
"从服务器获取最新配置");
|
||||
|
||||
manager.StartItem("network.config", "正在连接服务器...");
|
||||
|
||||
try
|
||||
{
|
||||
// 模拟网络请求
|
||||
await Task.Delay(1000);
|
||||
|
||||
manager.UpdateProgress("network.config", 50, "正在下载数据...");
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
manager.CompleteItem("network.config", "配置数据已更新");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
manager.FailItem("network.config", "网络请求失败", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:带超时的加载
|
||||
/// </summary>
|
||||
public static async Task LoadWithTimeoutExample(
|
||||
LoadingStateManager manager,
|
||||
LoadingTimeoutHandler timeoutHandler)
|
||||
{
|
||||
// 设置超时时间为 10 秒
|
||||
timeoutHandler.SetItemTimeout("data.heavy", TimeSpan.FromSeconds(10));
|
||||
|
||||
// 注册加载项
|
||||
manager.RegisterItem(
|
||||
"data.heavy",
|
||||
LoadingItemType.Data,
|
||||
"大数据处理",
|
||||
"处理大量数据,可能需要较长时间");
|
||||
|
||||
// 订阅超时事件
|
||||
timeoutHandler.ItemTimeout += (s, e) =>
|
||||
{
|
||||
Console.WriteLine($"加载项 '{e.ItemName}' 超时!");
|
||||
};
|
||||
|
||||
timeoutHandler.ItemRetry += (s, e) =>
|
||||
{
|
||||
Console.WriteLine($"正在重试 '{e.ItemName}' ({e.RetryCount}/{e.MaxRetryCount})...");
|
||||
};
|
||||
|
||||
// 开始加载
|
||||
manager.StartItem("data.heavy", "正在处理数据...");
|
||||
|
||||
// 模拟长时间操作
|
||||
await Task.Delay(15000);
|
||||
|
||||
// 完成
|
||||
manager.CompleteItem("data.heavy", "数据处理完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:完整启动流程
|
||||
/// </summary>
|
||||
public static async Task FullStartupExample(
|
||||
LoadingStateManager manager,
|
||||
LoadingStateReporter reporter,
|
||||
LoadingTimeoutHandler timeoutHandler)
|
||||
{
|
||||
// 启动超时处理器
|
||||
timeoutHandler.Start();
|
||||
|
||||
// 设置阶段
|
||||
manager.SetStage(StartupStage.Initializing, "开始初始化...");
|
||||
|
||||
// 1. 系统初始化
|
||||
manager.RegisterItem("system.init", LoadingItemType.System, "系统初始化");
|
||||
manager.StartItem("system.init");
|
||||
await Task.Delay(500);
|
||||
manager.CompleteItem("system.init");
|
||||
|
||||
// 2. 加载设置
|
||||
manager.SetStage(StartupStage.LoadingSettings, "正在加载设置...");
|
||||
manager.RegisterItem("settings.load", LoadingItemType.Settings, "用户设置");
|
||||
manager.StartItem("settings.load");
|
||||
await Task.Delay(800);
|
||||
manager.CompleteItem("settings.load");
|
||||
|
||||
// 3. 加载插件
|
||||
manager.SetStage(StartupStage.LoadingPlugins, "正在加载插件...");
|
||||
await LoadPluginsExample(manager);
|
||||
|
||||
// 4. 加载组件
|
||||
await LoadComponentsExample(manager);
|
||||
|
||||
// 5. 加载网络资源
|
||||
await LoadNetworkResourcesExample(manager);
|
||||
|
||||
// 6. 初始化界面
|
||||
manager.SetStage(StartupStage.InitializingUI, "正在初始化界面...");
|
||||
manager.RegisterItem("ui.init", LoadingItemType.System, "界面初始化");
|
||||
manager.StartItem("ui.init");
|
||||
await Task.Delay(600);
|
||||
manager.CompleteItem("ui.init");
|
||||
|
||||
// 完成
|
||||
manager.SetStage(StartupStage.Ready, "加载完成");
|
||||
|
||||
// 停止超时处理器
|
||||
timeoutHandler.Stop();
|
||||
}
|
||||
}
|
||||
274
LanMountainDesktop/Services/Loading/LoadingTimeoutHandler.cs
Normal file
274
LanMountainDesktop/Services/Loading/LoadingTimeoutHandler.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using System.Timers;
|
||||
|
||||
namespace LanMountainDesktop.Services.Loading;
|
||||
|
||||
/// <summary>
|
||||
/// 加载超时处理器 - 监控加载项超时并执行相应处理
|
||||
/// </summary>
|
||||
public class LoadingTimeoutHandler : IDisposable
|
||||
{
|
||||
private readonly LoadingStateManager _manager;
|
||||
private readonly System.Timers.Timer _checkTimer;
|
||||
private readonly Dictionary<string, TimeSpan> _itemTimeouts = new();
|
||||
private readonly Dictionary<string, int> _retryCounts = new();
|
||||
private readonly object _lock = new();
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// 默认超时时间
|
||||
/// </summary>
|
||||
public TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// 最大重试次数
|
||||
/// </summary>
|
||||
public int MaxRetryCount { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 检查间隔
|
||||
/// </summary>
|
||||
public TimeSpan CheckInterval { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// 超时事件
|
||||
/// </summary>
|
||||
public event EventHandler<LoadingTimeoutEventArgs>? ItemTimeout;
|
||||
|
||||
/// <summary>
|
||||
/// 重试事件
|
||||
/// </summary>
|
||||
public event EventHandler<LoadingRetryEventArgs>? ItemRetry;
|
||||
|
||||
/// <summary>
|
||||
/// 最终失败事件(超过最大重试次数)
|
||||
/// </summary>
|
||||
public event EventHandler<LoadingTimeoutEventArgs>? ItemFailed;
|
||||
|
||||
public LoadingTimeoutHandler(LoadingStateManager manager)
|
||||
{
|
||||
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
|
||||
|
||||
_checkTimer = new System.Timers.Timer(CheckInterval.TotalMilliseconds);
|
||||
_checkTimer.Elapsed += OnCheckTimerElapsed;
|
||||
_checkTimer.AutoReset = true;
|
||||
|
||||
// 订阅状态变更事件
|
||||
_manager.StateChanged += OnStateChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动监控
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_checkTimer.Start();
|
||||
AppLogger.Info("LoadingTimeoutHandler", "Timeout handler started");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止监控
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_checkTimer.Stop();
|
||||
AppLogger.Info("LoadingTimeoutHandler", "Timeout handler stopped");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为特定加载项设置超时
|
||||
/// </summary>
|
||||
public void SetItemTimeout(string itemId, TimeSpan timeout)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_itemTimeouts[itemId] = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取加载项的超时时间
|
||||
/// </summary>
|
||||
public TimeSpan GetItemTimeout(string itemId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _itemTimeouts.TryGetValue(itemId, out var timeout) ? timeout : DefaultTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置重试计数
|
||||
/// </summary>
|
||||
public void ResetRetryCount(string itemId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_retryCounts[itemId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时检查超时
|
||||
/// </summary>
|
||||
private void OnCheckTimerElapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
try
|
||||
{
|
||||
var activeItems = _manager.GetActiveItems().ToList();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
foreach (var item in activeItems)
|
||||
{
|
||||
if (!item.StartTime.HasValue) continue;
|
||||
|
||||
var timeout = GetItemTimeout(item.Id);
|
||||
var elapsed = now - item.StartTime.Value;
|
||||
|
||||
if (elapsed > timeout)
|
||||
{
|
||||
HandleTimeout(item.Id, elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("LoadingTimeoutHandler", $"Error checking timeouts: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理超时
|
||||
/// </summary>
|
||||
private void HandleTimeout(string itemId, TimeSpan elapsed)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var retryCount = _retryCounts.GetValueOrDefault(itemId, 0);
|
||||
|
||||
if (retryCount < MaxRetryCount)
|
||||
{
|
||||
// 重试
|
||||
_retryCounts[itemId] = retryCount + 1;
|
||||
|
||||
var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
|
||||
if (item != null)
|
||||
{
|
||||
AppLogger.Warn("LoadingTimeoutHandler",
|
||||
$"Item '{item.Name}' timed out after {elapsed.TotalSeconds}s, retrying ({retryCount + 1}/{MaxRetryCount})...");
|
||||
|
||||
ItemRetry?.Invoke(this, new LoadingRetryEventArgs
|
||||
{
|
||||
ItemId = itemId,
|
||||
ItemName = item.Name,
|
||||
RetryCount = retryCount + 1,
|
||||
MaxRetryCount = MaxRetryCount,
|
||||
ElapsedTime = elapsed
|
||||
});
|
||||
|
||||
// 重新启动该项
|
||||
_manager.StartItem(itemId, $"第 {retryCount + 1} 次重试...");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 最终失败
|
||||
_retryCounts.Remove(itemId);
|
||||
|
||||
var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
|
||||
if (item != null)
|
||||
{
|
||||
AppLogger.Error("LoadingTimeoutHandler",
|
||||
$"Item '{item.Name}' failed after {MaxRetryCount} retries ({elapsed.TotalSeconds}s)");
|
||||
|
||||
var args = new LoadingTimeoutEventArgs
|
||||
{
|
||||
ItemId = itemId,
|
||||
ItemName = item.Name,
|
||||
ElapsedTime = elapsed,
|
||||
RetryCount = MaxRetryCount,
|
||||
IsFinalFailure = true
|
||||
};
|
||||
|
||||
ItemTimeout?.Invoke(this, args);
|
||||
ItemFailed?.Invoke(this, args);
|
||||
|
||||
// 标记为失败
|
||||
_manager.FailItem(itemId,
|
||||
$"加载超时(超过 {elapsed.TotalSeconds:F0} 秒)",
|
||||
$"已重试 {MaxRetryCount} 次但仍失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更事件处理
|
||||
/// </summary>
|
||||
private void OnStateChanged(object? sender, LoadingStateChangedEventArgs e)
|
||||
{
|
||||
// 当项完成或失败时,清除重试计数
|
||||
if (e.CurrentState is LoadingState.Completed or LoadingState.Failed or LoadingState.Cancelled)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_retryCounts.Remove(e.Item.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// 当项开始时,如果是第一次开始,初始化重试计数
|
||||
if (e.CurrentState == LoadingState.InProgress &&
|
||||
(e.PreviousState == null || e.PreviousState == LoadingState.Pending))
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_retryCounts.ContainsKey(e.Item.Id))
|
||||
{
|
||||
_retryCounts[e.Item.Id] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
|
||||
Stop();
|
||||
|
||||
_checkTimer.Elapsed -= OnCheckTimerElapsed;
|
||||
_checkTimer.Dispose();
|
||||
|
||||
_manager.StateChanged -= OnStateChanged;
|
||||
|
||||
_itemTimeouts.Clear();
|
||||
_retryCounts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载超时事件参数
|
||||
/// </summary>
|
||||
public class LoadingTimeoutEventArgs : EventArgs
|
||||
{
|
||||
public required string ItemId { get; init; }
|
||||
public required string ItemName { get; init; }
|
||||
public required TimeSpan ElapsedTime { get; init; }
|
||||
public int RetryCount { get; init; }
|
||||
public bool IsFinalFailure { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载重试事件参数
|
||||
/// </summary>
|
||||
public class LoadingRetryEventArgs : EventArgs
|
||||
{
|
||||
public required string ItemId { get; init; }
|
||||
public required string ItemName { get; init; }
|
||||
public required int RetryCount { get; init; }
|
||||
public required int MaxRetryCount { get; init; }
|
||||
public required TimeSpan ElapsedTime { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user