异步初始化优化是 ControlPannel 硬件控制面板 MVVM 架构中的关键技术,旨在提升高频异步事件(如每秒 20 次 JSON 硬件状态更新)、异步集合加载(如电感列表 chk2uH 到 chk2KuH)和多线程操作(如继电器状态 chkSwitch1 到 chkSwitch5、位置输入 txtUPos)的性能和可靠性。
结合异步延迟加载、依赖注入(DI)框架、MVVM 模式、并发控制、事件聚合、线程同步、事件压缩、事件去重、动态去重窗口、优先级筛选和死锁预防策略,异步初始化优化通过减少阻塞、控制资源分配和处理异常,提升启动速度和用户体验。
本文将深入探讨异步初始化优化技巧在 ControlPannel 中的设计、实现、单元测试和注释,针对复杂硬件控制场景优化性能。
一、异步初始化优化的核心细节异步初始化优化通过精心设计的策略,减少异步操作(如硬件连接、数据加载)的开销,确保高频、实时硬件控制场景的效率和稳定性。
以下是异步初始化优化在 ControlPannel 中的核心细节:
- 异步初始化优化的作用:
- 减少启动阻塞:通过异步执行耗时初始化(如硬件连接或网络请求),避免 UI 线程阻塞。
- 优化资源分配:按需初始化服务,降低内存和 CPU 使用率。
- 提升容错性:通过重试、超时和降级机制处理初始化失败。
- 支持高并发:结合线程安全机制(如 SemaphoreSlim、ConcurrentDictionary),处理多线程初始化。
- 场景:ControlPannelViewModel 异步初始化 IHardwareService,仅在首次硬件状态轮询时建立连接。
- 异步初始化优化的设计:
- 异步延迟加载:使用 AsyncLazy<T> 封装异步初始化,确保首次访问时执行。
- 超时控制:为初始化设置超时(如 500ms),防止长时间阻塞。
- 重试机制:自动重试失败的初始化操作,增强可靠性。
- 条件初始化:根据运行时条件(如硬件可用性)决定是否初始化。
- 缓存结果:缓存初始化结果,减少重复操作。
- 场景:IHardwareService 异步初始化硬件连接,InductanceViewModel 异步加载电感列表。
- 优化点:
- 非阻塞 UI:使用 async/await 和 Dispatcher 确保 UI 响应性。
- 动态资源管理:根据负载动态调整初始化优先级和窗口。
- 错误隔离:捕获初始化异常,提供日志记录和降级逻辑。
- 性能监控:记录初始化时间和成功率,优化高频场景。
- 并行初始化:支持多个服务的并行异步初始化,减少总时间。
- 应用场景:
- 高频状态更新:ControlPannelViewModel 异步初始化 IHardwareService,延迟硬件连接。
- 异步集合加载:InductanceViewModel 异步初始化电感数据源。
- 用户交互:RelayViewModel 异步初始化 IEventAggregator,仅在事件发布时加载。
- 状态机同步:HardwareStateMachine 异步初始化状态表。
二、异步初始化优化实现以下是基于 ControlPannel 的异步初始化优化实现,扩展前述异步延迟加载、DI 框架、MVVM 模式、并发控制和事件处理机制,优化高频事件处理、异步集合加载和 UI 更新。代码使用 AsyncLazy<T>、超时控制、重试机制和性能监控,确保线程安全和高效初始化。
1. 项目结构
- Models
- InductanceModel.cs
- DeviceState.cs
- ViewModels
- ControlPannelViewModel.cs
- InductanceViewModel.cs
- RelayViewModel.cs
- Services
- IHardwareService.cs
- HardwareService.cs
- IEventAggregator.cs
- EventAggregator.cs
- HardwareStateMachine.cs
- Events
- HardwareStateEvent.cs
- Helpers
- RelayCommand.cs
- BulkObservableCollection.cs
- ConcurrentPriorityQueue.cs
- AsyncLazy.cs
- Logging
- IAsyncInitLogger.cs
- AsyncInitLogger.cs
- Converters
- BoolToColorConverter.cs
- Validation
- FloatValidationRule.cs
- Views
- ControlPannel.xaml
- ControlPannel.xaml.cs
- Tests
- AsyncInitializationTests.cs
- App.xaml.cs (DI 容器初始化)
2. 异步延迟加载工具类AsyncLazy.cs
using System;
using System.Threading.Tasks;
namespace ROBOT.AvatarUI.Helpers
{
public class AsyncLazy<T>
{
private readonly Lazy<Task<T>> _lazy;
private readonly TimeSpan _timeout;
public AsyncLazy(Func<Task<T>> factory, TimeSpan timeout, bool isThreadSafe = true)
{
_lazy = new Lazy<Task<T>>(factory, isThreadSafe);
_timeout = timeout;
}
public async Task<T> Value
{
get
{
try
{
return await Task.WhenAny(_lazy.Value, Task.Delay(_timeout)).ContinueWith(t =>
{
if (t == _lazy.Value)
return _lazy.Value.Result;
throw new TimeoutException("Async lazy initialization timeout");
});
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"AsyncLazy initialization error: {ex.Message}");
throw;
}
}
}
public bool IsValueCreated => _lazy.IsValueCreated;
}
}
3. 性能监控日志IAsyncInitLogger.cs
using System;
using System.Threading.Tasks;
namespace ROBOT.AvatarUI.Logging
{
public interface IAsyncInitLogger
{
Task LogAsync(string serviceName, TimeSpan duration, bool success, string errorMessage = null);
}
}
AsyncInitLogger.cs
using System;
using System.Threading.Tasks;
namespace ROBOT.AvatarUI.Logging
{
public class AsyncInitLogger : IAsyncInitLogger
{
public async Task LogAsync(string serviceName, TimeSpan duration, bool success, string errorMessage = null)
{
await Task.Run(() => System.Diagnostics.Debug.WriteLine(
$"Async initialized {serviceName} in {duration.TotalMilliseconds} ms, Success: {success}{(errorMessage != null ? $", Error: {errorMessage}" : "")}"));
}
}
}
4. DI 容器初始化(支持异步初始化优化)App.xaml.cs
using Microsoft.Extensions.DependencyInjection;
using ROBOT.AvatarUI.Services;
using ROBOT.AvatarUI.ViewModels;
using ROBOT.AvatarUI.Logging;
using System;
using System.Threading.Tasks;
using System.Windows;
using ROBOT.AvatarUI.Helpers;
namespace ROBOT.AvatarUI
{
public partial class App : Application
{
public IServiceProvider ServiceProvider { get; private set; }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 初始化 DI 容器
IServiceCollection services = new ServiceCollection();
// 注册日志服务
services.AddSingleton<IAsyncInitLogger, AsyncInitLogger>();
// 注册异步延迟加载服务
services.AddSingleton<AsyncLazy<IHardwareService>>(sp => new AsyncLazy<IHardwareService>(async () =>
{
var logger = sp.GetService<IAsyncInitLogger>();
var stopwatch = Stopwatch.StartNew();
try
{
var service = sp.GetService<HardwareService>();
await service.InitializeAsync();
await logger.LogAsync(nameof(IHardwareService), stopwatch.Elapsed, true);
return service;
}
catch (Exception ex)
{
await logger.LogAsync(nameof(IHardwareService), stopwatch.Elapsed, false, ex.Message);
throw;
}
}, TimeSpan.FromMilliseconds(500)));
services.AddSingleton<IHardwareService, HardwareService>();
services.AddSingleton<AsyncLazy<IEventAggregator>>(sp => new AsyncLazy<IEventAggregator>(async () =>
{
var logger = sp.GetService<IAsyncInitLogger>();
var stopwatch = Stopwatch.StartNew();
try
{
var aggregator = sp.GetService<EventAggregator>();
await Task.Delay(50); // 模拟异步初始化
await logger.LogAsync(nameof(IEventAggregator), stopwatch.Elapsed, true);
return aggregator;
}
catch (Exception ex)
{
await logger.LogAsync(nameof(IEventAggregator), stopwatch.Elapsed, false, ex.Message);
throw;
}
}, TimeSpan.FromMilliseconds(500)));
services.AddSingleton<IEventAggregator, EventAggregator>();
services.AddSingleton<HardwareStateMachine>();
services.AddTransient<ControlPannelViewModel>();
services.AddTransient<InductanceViewModel>();
services.AddTransient<RelayViewModel>();
// 构建服务提供者
ServiceProvider = services.BuildServiceProvider();
// 设置主窗口
var mainWindow = new Views.ControlPannel
{
DataContext = ServiceProvider.GetService<ControlPannelViewModel>()
};
mainWindow.Show();
}
}
}
5. 服务实现(支持异步初始化)IHardwareService.cs
using System.Threading.Tasks;
namespace ROBOT.AvatarUI.Services
{
public interface IHardwareService
{
Task InitializeAsync();
Task<string> GetHardwareStateAsync();
void SendServoCtrlCommand(bool isOn);
void SendInductCtrlCommand(string command);
}
}
HardwareService.cs
using System.Threading.Tasks;
namespace ROBOT.AvatarUI.Services
{
public class HardwareService : IHardwareService
{
public async Task InitializeAsync()
{
await Task.Delay(100); // 模拟硬件连接初始化
System.Diagnostics.Debug.WriteLine("HardwareService initialized");
}
public async Task<string> GetHardwareStateAsync()
{
await Task.Delay(100); // 模拟异步延迟
return "{\"status\": 1, \"inductance\": \"100uH\", \"relays\": [true, false], \"position\": 10.5}";
}
public void SendServoCtrlCommand(bool isOn)
{
System.Diagnostics.Debug.WriteLine($"Servo {(isOn ? "On" : "Off")}");
}
public void SendInductCtrlCommand(string command)
{
System.Diagnostics.Debug.WriteLine($"Induct Command: {command}");
}
}
}
6. ViewModel(异步初始化优化)ControlPannelViewModel.cs
using Microsoft.Extensions.DependencyInjection;
using ROBOT.AvatarUI.Models;
using ROBOT.AvatarUI.Services;
using ROBOT.AvatarUI.Logging;
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using ROBOT.AvatarUI.Helpers;
namespace ROBOT.AvatarUI.ViewModels
{
public class ControlPannelViewModel : INotifyPropertyChanged
{
private readonly AsyncLazy<IHardwareService> _hardwareService;
private readonly AsyncLazy<IEventAggregator> _eventAggregator;
private readonly HardwareStateMachine _stateMachine;
private readonly DeviceState _deviceState;
private readonly DispatcherTimer _updateTimer;
private readonly ConcurrentPriorityQueue<int, string> _pendingStates;
private readonly ConcurrentDictionary<string, (HardwareStateEvent State, DateTime Timestamp, int Priority)> _stateCache;
private readonly IServiceProvider _serviceProvider;
private readonly IAsyncInitLogger _logger;
private bool _isUpdating;
private readonly SemaphoreSlim _updateSemaphore = new(1, 1);
private readonly CancellationTokenSource _statePollingCts = new();
private readonly TimeSpan _minDeduplicationWindow = TimeSpan.FromMilliseconds(50);
private readonly TimeSpan _maxDeduplicationWindow = TimeSpan.FromMilliseconds(200);
private readonly TimeSpan _lockTimeout = TimeSpan.FromMilliseconds(500);
public ControlPannelViewModel(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_hardwareService = serviceProvider.GetService<AsyncLazy<IHardwareService>>();
_eventAggregator = serviceProvider.GetService<AsyncLazy<IEventAggregator>>();
_stateMachine = serviceProvider.GetService<HardwareStateMachine>();
_logger = serviceProvider.GetService<IAsyncInitLogger>();
_deviceState = new DeviceState();
_pendingStates = new ConcurrentPriorityQueue<int, string>();
_stateCache = new ConcurrentDictionary<string, (HardwareStateEvent, DateTime, int)>();
InductanceViewModel = serviceProvider.GetService<InductanceViewModel>();
RelayViewModel = serviceProvider.GetService<RelayViewModel>();
_updateTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(500),
IsEnabled = false
};
_updateTimer.Tick += async (s, e) => await ProcessPendingStateAsync();
ToggleServoCommand = new RelayCommand(o => ToggleServo(), o => _stateMachine.CanToggleServo);
SetUPhaseCommand = new RelayCommand(o => SetPhase(1), o => _stateMachine.CanSelectPhase);
SetPositionCommand = new RelayCommand(o => SetPosition(), o => CanSetPosition());
InitializeEventAggregatorAsync().GetAwaiter().GetResult();
}
public InductanceViewModel InductanceViewModel { get; }
public RelayViewModel RelayViewModel { get; }
public bool IsServoOn
{
get => _deviceState.IsServoOn;
set
{
if (_deviceState.IsServoOn == value) return;
if (Monitor.TryEnter(_deviceState, _lockTimeout))
{
try
{
_deviceState.IsServoOn = value;
OnPropertyChanged();
}
finally
{
Monitor.Exit(_deviceState);
}
}
else
{
System.Diagnostics.Debug.WriteLine("IsServoOn lock timeout");
return;
}
Task.Run(async () =>
{
var stopwatch = Stopwatch.StartNew();
try
{
var hardwareService = await _hardwareService.Value;
hardwareService.SendServoCtrlCommand(value);
var eventAggregator = await _eventAggregator.Value;
await eventAggregator.PublishAsync(new HardwareStateEvent
{
ServoState = value,
Priority = 1,
CancellationToken = CancellationToken.None
});
await _logger.LogAsync("ServoCommand", stopwatch.Elapsed, true);
}
catch (Exception ex)
{
await _logger.LogAsync("ServoCommand", stopwatch.Elapsed, false, ex.Message);
}
}).GetAwaiter().GetResult();
}
}
public double UPosition
{
get => _deviceState.UPosition;
set
{
if (_deviceState.UPosition == value) return;
if (Monitor.TryEnter(_deviceState, _lockTimeout))
{
try
{
_deviceState.UPosition = value;
OnPropertyChanged();
}
finally
{
Monitor.Exit(_deviceState);
}
}
else
{
System.Diagnostics.Debug.WriteLine("UPosition lock timeout");
return;
}
CommandManager.InvalidateRequerySuggested();
Task.Run(async () =>
{
var stopwatch = Stopwatch.StartNew();
try
{
var eventAggregator = await _eventAggregator.Value;
await eventAggregator.PublishAsync(new HardwareStateEvent
{
Position = value,
Priority = 1,
CancellationToken = CancellationToken.None
});
await _logger.LogAsync("PositionUpdate", stopwatch.Elapsed, true);
}
catch (Exception ex)
{
await _logger.LogAsync("PositionUpdate", stopwatch.Elapsed, false, ex.Message);
}
}).GetAwaiter().GetResult();
}
}
public ICommand ToggleServoCommand { get; }
public ICommand SetUPhaseCommand { get; }
public ICommand SetPositionCommand { get; }
private async Task InitializeEventAggregatorAsync()
{
var stopwatch = Stopwatch.StartNew();
try
{
var eventAggregator = await _eventAggregator.Value;
eventAggregator.SubscribeAsync<HardwareStateEvent>(HandleHardwareStateAsync, priority: 0);
await _logger.LogAsync("EventAggregatorSubscription", stopwatch.Elapsed, true);
}
catch (Exception ex)
{
await _logger.LogAsync("EventAggregatorSubscription", stopwatch.Elapsed, false, ex.Message);
throw;
}
}
private void ToggleServo()
{
if (Monitor.TryEnter(_deviceState, _lockTimeout))
{
try
{
if (Monitor.TryEnter(_stateMachine, _lockTimeout))
{
try
{
_stateMachine.ToggleServo();
IsServoOn = _stateMachine.IsServoOn;
}
finally
{
Monitor.Exit(_stateMachine);
}
}
else
{
System.Diagnostics.Debug.WriteLine("StateMachine lock timeout");
}
}
finally
{
Monitor.Exit(_deviceState);
}
}
else
{
System.Diagnostics.Debug.WriteLine("DeviceState lock timeout");
}
}
private void SetPhase(int phase)
{
if (Monitor.TryEnter(_stateMachine, _lockTimeout))
{
try
{
if (Monitor.TryEnter(_deviceState, _lockTimeout))
{
try
{
_stateMachine.SelectPhase();
_deviceState.PhaseState = phase;
OnPropertyChanged(nameof(PhaseState));
}
finally
{
Monitor.Exit(_deviceState);
}
}
else
{
System.Diagnostics.Debug.WriteLine("DeviceState lock timeout");
}
}
finally
{
Monitor.Exit(_stateMachine);
}
}
else
{
System.Diagnostics.Debug.WriteLine("StateMachine lock timeout");
}
}
private bool CanSetPosition()
{
if (Monitor.TryEnter(_stateMachine, _lockTimeout))
{
try
{
return _stateMachine.IsPhaseSelected && !double.IsNaN(UPosition);
}
finally
{
Monitor.Exit(_stateMachine);
}
}
System.Diagnostics.Debug.WriteLine("StateMachine lock timeout");
return false;
}
private async void SetPosition()
{
var stopwatch = Stopwatch.StartNew();
try
{
var hardwareService = await _hardwareService.Value;
hardwareService.SendInductCtrlCommand($"Position: {UPosition}");
await _logger.LogAsync("SetPosition", stopwatch.Elapsed, true);
}
catch (Exception ex)
{
await _logger.LogAsync("SetPosition", stopwatch.Elapsed, false, ex.Message);
}
}
private async void StartStatePolling()
{
while (!_statePollingCts.Token.IsCancellationRequested)
{
var stopwatch = Stopwatch.StartNew();
try
{
var hardwareService = await _hardwareService.Value;
string state = await hardwareService.GetHardwareStateAsync();
QueueStateUpdate(state);
await _logger.LogAsync("StatePolling", stopwatch.Elapsed, true);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"State Polling Error: {ex.Message}");
await _logger.LogAsync("StatePolling", stopwatch.Elapsed, false, ex.Message);
}
await Task.Delay(1000, _statePollingCts.Token);
}
}
public void QueueStateUpdate(string stateStr)
{
string stateKey = GenerateStateKey(stateStr);
TimeSpan deduplicationWindow = GetDynamicDeduplicationWindow();
HardwareStateEvent stateEvent;
int priority;
try
{
Newtonsoft.Json.Linq.JObject jObj = Newtonsoft.Json.Linq.JObject.Parse(stateStr);
stateEvent = new HardwareStateEvent
{
ServoState = (int)jObj["status"] == 1,
Inductance = jObj["inductance"]?.ToString(),
Relays = jObj["relays"]?.ToObject<bool[]>(),
Position = (double)jObj["position"],
Priority = 0,
CancellationToken = _statePollingCts.Token
};
priority = AdjustPriority(stateEvent);
stateEvent.Priority = priority;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Parse State Error: {ex.Message}");
return;
}
if (_stateCache.TryGetValue(stateKey, out (HardwareStateEvent State, DateTime Timestamp, int Priority) cached) &&
cached.Priority >= priority &&
(DateTime.Now - cached.Timestamp) < deduplicationWindow)
return;
_stateCache[stateKey] = (stateEvent, DateTime.Now, priority);
_pendingStates.Enqueue(priority, stateStr);
_updateTimer.Stop();
_updateTimer.Start();
}
private async Task ProcessPendingStateAsync()
{
bool acquired = await _updateSemaphore.WaitAsync(_lockTimeout, _statePollingCts.Token);
if (!acquired)
{
System.Diagnostics.Debug.WriteLine("Update semaphore timeout");
return;
}
try
{
_updateTimer.Stop();
if (_isUpdating) return;
_isUpdating = true;
Stopwatch stopwatch = Stopwatch.StartNew();
List<(int Priority, HardwareStateEvent State, string StateKey)> batch = [];
TimeSpan deduplicationWindow = GetDynamicDeduplicationWindow();
try
{
while (_pendingStates.TryDequeue(out int priority, out string stateStr))
{
string stateKey = GenerateStateKey(stateStr);
if (!_stateCache.TryGetValue(stateKey, out (HardwareStateEvent State, DateTime Timestamp, int Priority) cached) ||
cached.Priority < priority ||
(DateTime.Now - cached.Timestamp) > deduplicationWindow)
continue;
batch.Add((priority, cached.State, stateKey));
}
if (batch.Any())
{
await Task.Run(async () =>
{
var eventAggregator = await _eventAggregator.Value;
foreach ((int priority, HardwareStateEvent state, string stateKey) in batch.OrderByDescending(b => b.Priority))
{
if (state.CancellationToken.IsCancellationRequested) continue;
await Dispatcher.CurrentDispatcher.InvokeAsync(async () =>
{
await eventAggregator.PublishAsync(state, state.CancellationToken);
}, DispatcherPriority.Background, state.CancellationToken);
}
}, _statePollingCts.Token);
}
await _logger.LogAsync("ProcessPendingState", stopwatch.Elapsed, true);
}
catch (OperationCanceledException)
{
System.Diagnostics.Debug.WriteLine("State Update Canceled");
await _logger.LogAsync("ProcessPendingState", stopwatch.Elapsed, false, "Operation canceled");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Update States Error: {ex.Message}");
await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
UPosition = 0.0;
OnPropertyChanged(nameof(UPosition));
}, DispatcherPriority.Background);
await _logger.LogAsync("ProcessPendingState", stopwatch.Elapsed, false, ex.Message);
}
finally
{
_isUpdating = false;
stopwatch.Stop();
}
}
finally
{
if (acquired)
_updateSemaphore.Release();
}
}
private async Task HandleHardwareStateAsync(HardwareStateEvent state)
{
state.CancellationToken.ThrowIfCancellationRequested();
await Task.Run(async () =>
{
if (Monitor.TryEnter(_deviceState, _lockTimeout))
{
try
{
await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
if (_isUpdating) return;
IsServoOn = state.ServoState;
UPosition = state.Position;
}, DispatcherPriority.Background, state.CancellationToken);
}
finally
{
Monitor.Exit(_deviceState);
}
}
else
{
System.Diagnostics.Debug.WriteLine("DeviceState lock timeout in HandleHardwareStateAsync");
}
}, state.CancellationToken);
}
private TimeSpan GetDynamicDeduplicationWindow()
{
int queueSize = _pendingStates.Count;
double windowMs = Math.Min(_maxDeduplicationWindow.TotalMilliseconds, _minDeduplicationWindow.TotalMilliseconds + queueSize * 10);
return TimeSpan.FromMilliseconds(windowMs);
}
private int AdjustPriority(HardwareStateEvent state)
{
int basePriority = state.Priority;
if (state.ServoState) basePriority += 2;
if (!string.IsNullOrEmpty(state.Inductance)) basePriority += 1;
return basePriority;
}
private static string GenerateStateKey(string stateStr)
{
try
{
Newtonsoft.Json.Linq.JObject jObj = Newtonsoft.Json.Linq.JObject.Parse(stateStr);
return $"{(int)jObj["status"]}_{jObj["inductance"]}_{string.Join(",", jObj["relays"]?.ToObject<bool[]>() ?? [])}_{jObj["position"]}";
}
catch
{
return stateStr.GetHashCode().ToString();
}
}
public void Dispose()
{
_statePollingCts.Cancel();
_statePollingCts.Dispose();
_updateTimer.Stop();
_pendingStates.Clear();
_stateCache.Clear();
_updateSemaphore.Dispose();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
7. 单元测试AsyncInitializationTests.cs
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using ROBOT.AvatarUI.Events;
using ROBOT.AvatarUI.Models;
using ROBOT.AvatarUI.Services;
using ROBOT.AvatarUI.ViewModels;
using ROBOT.AvatarUI.Logging;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using ROBOT.AvatarUI.Helpers;
namespace ROBOT.AvatarUI.Tests
{
public class AsyncInitializationTests
{
private readonly IServiceProvider _serviceProvider;
private readonly Mock<IHardwareService> _hardwareServiceMock;
private readonly Mock<IAsyncInitLogger> _loggerMock;
private readonly EventAggregator _eventAggregator;
private readonly HardwareStateMachine _stateMachine;
public AsyncInitializationTests()
{
_hardwareServiceMock = new Mock<IHardwareService>();
_loggerMock = new Mock<IAsyncInitLogger>();
_eventAggregator = new EventAggregator();
_stateMachine = new HardwareStateMachine();
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IAsyncInitLogger>(_loggerMock.Object);
services.AddSingleton<AsyncLazy<IHardwareService>>(sp => new AsyncLazy<IHardwareService>(async () =>
{
await _hardwareServiceMock.Object.InitializeAsync();
return _hardwareServiceMock.Object;
}, TimeSpan.FromMilliseconds(500)));
services.AddSingleton<IHardwareService>(_hardwareServiceMock.Object);
services.AddSingleton<AsyncLazy<IEventAggregator>>(sp => new AsyncLazy<IEventAggregator>(async () =>
{
await Task.Delay(50);
return _eventAggregator;
}, TimeSpan.FromMilliseconds(500)));
services.AddSingleton<IEventAggregator>(_eventAggregator);
services.AddSingleton<HardwareStateMachine>(_stateMachine);
services.AddTransient<ControlPannelViewModel>();
services.AddTransient<InductanceViewModel>();
services.AddTransient<RelayViewModel>();
_serviceProvider = services.BuildServiceProvider();
}
[Fact]
public void ControlPannelViewModel_AsyncInitialization_NotInitializedOnConstruction()
{
// Arrange
_hardwareServiceMock.Setup(h => h.InitializeAsync()).Returns(Task.CompletedTask);
// Act
var viewModel = _serviceProvider.GetService<ControlPannelViewModel>();
// Assert
_hardwareServiceMock.Verify(h => h.InitializeAsync(), Times.Never());
}
[Fact]
public async Task QueueStateUpdate_AsyncInitialization_InitializesOnFirstCall()
{
// Arrange
var viewModel = _serviceProvider.GetService<ControlPannelViewModel>();
string json = "{\"status\": 1, \"inductance\": \"100uH\", \"relays\": [true, false], \"position\": 10.5}";
_hardwareServiceMock.Setup(h => h.InitializeAsync()).Returns(Task.CompletedTask);
_hardwareServiceMock.Setup(h => h.GetHardwareStateAsync()).ReturnsAsync(json);
viewModel.InductanceViewModel.Inductances.AddRange(new[]
{
new InductanceModel { Value = "100uH", IsSelected = false }
});
viewModel.RelayViewModel.RelayStates.AddRange(new bool[2]);
// Act
viewModel.QueueStateUpdate(json);
await Task.Delay(1000);
// Assert
viewModel.IsServoOn.Should().BeTrue();
viewModel.UPosition.Should().Be(10.5);
viewModel.InductanceViewModel.SelectedInductance.Value.Should().Be("100uH");
viewModel.RelayViewModel.RelayStates.Should().Equal(true, false);
_hardwareServiceMock.Verify(h => h.InitializeAsync(), Times.Once());
_loggerMock.Verify(l => l.LogAsync("StatePolling", It.IsAny<TimeSpan>(), true, null), Times.AtLeastOnce());
}
[Fact]
public async Task ToggleServoCommand_AsyncInitialization_InitializesOnFirstCall()
{
// Arrange
var viewModel = _serviceProvider.GetService<ControlPannelViewModel>();
viewModel.IsServoOn = false;
_hardwareServiceMock.Setup(h => h.InitializeAsync()).Returns(Task.CompletedTask);
_hardwareServiceMock.Setup(h => h.SendServoCtrlCommand(true));
// Act
viewModel.ToggleServoCommand.Execute(null);
await Task.Delay(100);
// Assert
viewModel.IsServoOn.Should().BeTrue();
_hardwareServiceMock.Verify(h => h.InitializeAsync(), Times.Once());
_hardwareServiceMock.Verify(h => h.SendServoCtrlCommand(true), Times.Once());
_loggerMock.Verify(l => l.LogAsync("ServoCommand", It.IsAny<TimeSpan>(), true, null), Times.Once());
}
[Fact]
public async Task AsyncInitialization_ConcurrentAccess_ThreadSafe()
{
// Arrange
var viewModel = _serviceProvider.GetService<ControlPannelViewModel>();
string json = "{\"status\": 1, \"inductance\": \"100uH\", \"relays\": [true, false], \"position\": 10.5}";
_hardwareServiceMock.Setup(h => h.InitializeAsync()).Returns(Task.CompletedTask);
_hardwareServiceMock.Setup(h => h.GetHardwareStateAsync()).ReturnsAsync(json);
viewModel.InductanceViewModel.Inductances.AddRange(new[]
{
new InductanceModel { Value = "100uH", IsSelected = false }
});
viewModel.RelayViewModel.RelayStates.AddRange(new bool[2]);
// Act
Task[] tasks = Enumerable.Range(0, 50)
.Select(_ => Task.Run(() => viewModel.QueueStateUpdate(json)))
.ToArray();
await Task.WhenAll(tasks);
await Task.Delay(1000);
// Assert
viewModel.IsServoOn.Should().BeTrue();
viewModel.UPosition.Should().Be(10.5);
viewModel.InductanceViewModel.SelectedInductance.Value.Should().Be("100uH");
_hardwareServiceMock.Verify(h => h.InitializeAsync(), Times.Once()); // 仅初始化一次
_loggerMock.Verify(l => l.LogAsync("StatePolling", It.IsAny<TimeSpan>(), true, null), Times.AtLeastOnce());
}
[Fact]
public async Task AsyncInitialization_Timeout_HandlesGracefully()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<IAsyncInitLogger>(_loggerMock.Object);
services.AddSingleton<AsyncLazy<IHardwareService>>(sp => new AsyncLazy<IHardwareService>(async () =>
{
await Task.Delay(1000); // 模拟超时
throw new TimeoutException("Initialization timeout");
}, TimeSpan.FromMilliseconds(500)));
services.AddSingleton<IHardwareService>(_hardwareServiceMock.Object);
services.AddSingleton<IEventAggregator>(_eventAggregator);
services.AddSingleton<HardwareStateMachine>(_stateMachine);
services.AddTransient<ControlPannelViewModel>();
services.AddTransient<InductanceViewModel>();
services.AddTransient<RelayViewModel>();
var serviceProvider = services.BuildServiceProvider();
var viewModel = serviceProvider.GetService<ControlPannelViewModel>();
string json = "{\"status\": 1, \"inductance\": \"100uH\", \"relays\": [true, false], \"position\": 10.5}";
// Act
viewModel.QueueStateUpdate(json);
await Task.Delay(1500);
// Assert
viewModel.IsServoOn.Should().BeFalse(); // 未成功初始化
viewModel.UPosition.Should().Be(0.0);
_loggerMock.Verify(l => l.LogAsync(nameof(IHardwareService), It.IsAny<TimeSpan>(), false, It.IsAny<string>()), Times.Once());
}
[Fact]
public async Task AsyncInitialization_RetryMechanism_RecoversFromFailure()
{
// Arrange
var retryCount = 0;
_hardwareServiceMock.Setup(h => h.InitializeAsync()).Returns(async () =>
{
if (retryCount++ < 2)
throw new InvalidOperationException("Initialization failed");
await Task.Delay(100);
});
var viewModel = _serviceProvider.GetService<ControlPannelViewModel>();
string json = "{\"status\": 1, \"inductance\": \"100uH\", \"relays\": [true, false], \"position\": 10.5}";
_hardwareServiceMock.Setup(h => h.GetHardwareStateAsync()).ReturnsAsync(json);
viewModel.InductanceViewModel.Inductances.AddRange(new[]
{
new InductanceModel { Value = "100uH", IsSelected = false }
});
viewModel.RelayViewModel.RelayStates.AddRange(new bool[2]);
// Act
viewModel.QueueStateUpdate(json);
await Task.Delay(1000);
// Assert
viewModel.IsServoOn.Should().BeTrue();
_hardwareServiceMock.Verify(h => h.InitializeAsync(), Times.Exactly(3)); // 重试 2 次后成功
_loggerMock.Verify(l => l.LogAsync("StatePolling", It.IsAny<TimeSpan>(), true, null), Times.AtLeastOnce());
}
}
}
8. 中文解释
- 异步初始化优化机制:
- AsyncLazy<T>:封装 Lazy<Task<T>>,支持异步初始化,设置 500ms 超时。
- 重试机制:自动重试失败的初始化操作(默认 3 次,100ms 间隔)。
- 超时控制:AsyncLazy<T> 使用 Task.WhenAny 限制初始化时间。
- 性能监控:IAsyncInitLogger 记录初始化时间和成功/失败状态。
- 条件初始化:根据硬件状态或用户操作动态决定初始化。
- 实现细节:
- DI 容器:App.xaml.cs 注册 AsyncLazy<IHardwareService> 和 IAsyncInitLogger,支持异步初始化和日志。
- ViewModel:ControlPannelViewModel 使用 _hardwareService.Value 和 _eventAggregator.Value 异步访问服务。
- 异步方法:StartStatePolling、SetPosition 和 InitializeEventAggregatorAsync 使用 await 处理异步初始化。
- 线程安全:Lazy<Task<T>> 和 SemaphoreSlim 确保多线程安全。
- 单元测试:
- 测试异步初始化行为,验证服务未在构造时初始化。
- 模拟高并发(50 次并发更新),验证线程安全和单次初始化。
- 测试超时和重试场景,验证错误隔离和恢复能力。
- 使用 FluentAssertions 和 Moq 确保测试清晰。
- 优化点:
- 非阻塞 UI:Dispatcher.InvokeAsync 确保 UI 更新线程安全。
- 动态资源管理:根据 ConcurrentPriorityQueue 队列大小调整去重窗口。
- 错误隔离:捕获初始化异常,记录日志并重置状态(如 UPosition = 0.0)。
- 并行初始化:支持多个服务的并行异步初始化。
三、额外异步初始化优化技巧
- 重试机制:
- 实现通用重试逻辑:csharp
private async Task<T> InitializeWithRetryAsync<T>(Func<Task<T>> factory, int retryCount = 3, TimeSpan retryDelay = default) { for (int i = 0; i < retryCount; i++) { var stopwatch = Stopwatch.StartNew(); try { var result = await factory(); await _logger.LogAsync($"Retry_{typeof(T).Name}", stopwatch.Elapsed, true); return result; } catch (Exception ex) { await _logger.LogAsync($"Retry_{typeof(T).Name}", stopwatch.Elapsed, false, ex.Message); if (i < retryCount - 1) await Task.Delay(retryDelay); } } throw new InvalidOperationException($"Failed to initialize {typeof(T).Name} after {retryCount} retries"); } public ControlPannelViewModel(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _logger = serviceProvider.GetService<IAsyncInitLogger>(); _hardwareService = new AsyncLazy<IHardwareService>(() => InitializeWithRetryAsync(async () => { var service = serviceProvider.GetService<IHardwareService>(); await service.InitializeAsync(); return service; }, retryCount: 3, retryDelay: TimeSpan.FromMilliseconds(100)), TimeSpan.FromMilliseconds(500)); // 其他初始化 }
- 实现通用重试逻辑:csharp
- 并行初始化:
- 并行初始化多个服务:csharp
private async Task InitializeServicesAsync() { var tasks = new[] { _hardwareService.Value, _eventAggregator.Value }; await Task.WhenAll(tasks); await _logger.LogAsync("ParallelInitialization", TimeSpan.FromTicks(tasks.Sum(t => t.Result.GetHashCode())), true); } public ControlPannelViewModel(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _logger = serviceProvider.GetService<IAsyncInitLogger>(); _hardwareService = new AsyncLazy<IHardwareService>(/* ... */); _eventAggregator = new AsyncLazy<IEventAggregator>(/* ... */); InitializeServicesAsync().GetAwaiter().GetResult(); // 其他初始化 }
- 并行初始化多个服务:csharp
- 条件初始化:
- 根据硬件状态动态初始化:csharp
private bool _isHardwareRequired; private AsyncLazy<IHardwareService> _conditionalHardwareService; public ControlPannelViewModel(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _logger = serviceProvider.GetService<IAsyncInitLogger>(); _conditionalHardwareService = new AsyncLazy<IHardwareService>(async () => { if (!_isHardwareRequired) return null; var service = serviceProvider.GetService<IHardwareService>(); await service.InitializeAsync(); return service; }, TimeSpan.FromMilliseconds(500)); // 其他初始化 } private async Task InitializeHardwareAsync() { _isHardwareRequired = true; var hardwareService = await _conditionalHardwareService.Value; if (hardwareService != null) await hardwareService.GetHardwareStateAsync(); }
- 根据硬件状态动态初始化:csharp
- 初始化优先级:
- 根据服务优先级动态调整初始化顺序:csharp
private readonly ConcurrentPriorityQueue<int, Func<Task>> _initTasks = new(); private void QueueInitialization(Func<Task> initTask, int priority) { _initTasks.Enqueue(priority, initTask); } private async Task ExecuteInitializationAsync() { while (_initTasks.TryDequeue(out int priority, out var initTask)) { var stopwatch = Stopwatch.StartNew(); try { await initTask(); await _logger.LogAsync($"InitTask_Priority_{priority}", stopwatch.Elapsed, true); } catch (Exception ex) { await _logger.LogAsync($"InitTask_Priority_{priority}", stopwatch.Elapsed, false, ex.Message); } } } public ControlPannelViewModel(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _logger = serviceProvider.GetService<IAsyncInitLogger>(); QueueInitialization(async () => { var service = await _hardwareService.Value; await service.InitializeAsync(); }, priority: 2); QueueInitialization(async () => { var aggregator = await _eventAggregator.Value; await Task.Delay(50); }, priority: 1); ExecuteInitializationAsync().GetAwaiter().GetResult(); // 其他初始化 }
- 根据服务优先级动态调整初始化顺序:csharp
- 降级机制:
- 提供初始化失败的降级逻辑:csharp
private async Task<IHardwareService> InitializeWithFallbackAsync() { try { return await _hardwareService.Value; } catch (Exception ex) { await _logger.LogAsync(nameof(IHardwareService), TimeSpan.Zero, false, $"Falling back: {ex.Message}"); return new FallbackHardwareService(); // 降级服务 } } public class FallbackHardwareService : IHardwareService { public async Task InitializeAsync() => await Task.CompletedTask; public async Task<string> GetHardwareStateAsync() => "{\"status\": 0, \"inductance\": \"\", \"relays\": [], \"position\": 0.0}"; public void SendServoCtrlCommand(bool isOn) { } public void SendInductCtrlCommand(string command) { } } private async void StartStatePolling() { while (!_statePollingCts.Token.IsCancellationRequested) { try { var hardwareService = await InitializeWithFallbackAsync(); string state = await hardwareService.GetHardwareStateAsync(); QueueStateUpdate(state); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"State Polling Error: {ex.Message}"); } await Task.Delay(1000, _statePollingCts.Token); } }
- 提供初始化失败的降级逻辑:csharp
四、总结通过异步初始化优化的实现,ControlPannel 的 MVVM 架构更适合高频、实时硬件控制场景:
- AsyncLazy<T>:支持异步初始化,设置超时控制(500ms)。
- 重试机制:自动重试 3 次,100ms 间隔,提升可靠性。
- 性能监控:IAsyncInitLogger 记录初始化时间和状态。
- 并行初始化:支持多个服务并行初始化,减少总时间。
- 降级机制:提供 FallbackHardwareService 处理初始化失败。
建议:
- 测试高并发(50 次并发更新),验证线程安全和初始化单次性。
- 扩展 HardwareStateEvent 支持更多硬件状态。
- 集成 IAsyncInitLogger 监控初始化性能。
- 使用 CI/CD 运行单元测试,覆盖超时、重试和降级场景。
这些优化使异步初始化在 ControlPannel 中高效、健壮,满足复杂硬件控制需求。
、异步集合加载&spm=1001.2101.3001.5002&articleId=149567624&d=1&t=3&u=be18dc7605c84939bc118b1badae42be)
371

被折叠的 条评论
为什么被折叠?



