导航重载ViewModel时如何保留Task对象引用?
问题场景
导航机制:每次点击菜单项时,MainWindow.xaml.cs调用NavView_Navigate,通过<Frame x:Name="ContentFrame" IsTabStop="True"/>加载对应页面;页面内通过ViewModel = App.Current.Services.GetService<ObjectViewModel>()获取ViewModel实例。
ViewModel中通过Task? task控制异步脚本:点击按钮触发Start()启动任务,Stop()方法用于终止任务。但切换菜单项时,ViewModel被重新创建,Task引用丢失,导致无法调用Stop()终止运行中的任务。
核心问题
导航切换页面时,Transient生命周期的ViewModel被重新实例化,原有Task、CancellationTokenSource等状态引用丢失,无法对正在运行的任务进行控制。
相关代码
ViewModel实现
private readonly RobotService _service; CancellationTokenSource? cts; CancellationToken token; Task? task; public RobotViewModel(RobotService service) { _service = service; } public void Start() { if (SelectedObject != null) { cts = new CancellationTokenSource(); token = cts.Token; task = Task.Run(() => { _service.Run(SelectedObject); }, token); } } public void Stop() { if (task != null) { _service?.Stop(); if (!task.IsCompleted) { cts?.Cancel(); } } }
依赖注入注册
services.AddSingleton<MainWindow>(); services.AddSingleton<HomePage>(); services.AddSingleton<BedrijfseenheidPage>(); services.AddSingleton<SmtpPage>(); services.AddSingleton<RobotPage>(); services.AddTransient<BedrijfseenheidViewModel>(); services.AddTransient<SmtpViewModel>(); services.AddTransient<RobotViewModel>();
最佳实践策略
1. 将ViewModel改为Singleton生命周期
直接修改DI注册,把RobotViewModel的生命周期从Transient改为Singleton,确保导航切换时不会创建新实例,保留原有状态:
services.AddSingleton<RobotViewModel>();
注意:如果ViewModel包含页面特定的临时状态,需区分全局状态和页面临时状态,避免不同页面实例共享非预期状态。
2. 提取任务状态到独立的Singleton服务
创建一个专门的任务管理服务,负责持有Task、CancellationTokenSource等状态,将状态与ViewModel解耦:
public class RobotTaskManager { private CancellationTokenSource? _cts; private Task? _currentTask; private readonly RobotService _robotService; public RobotTaskManager(RobotService robotService) { _robotService = robotService; } public void Start(object selectedObject) { _cts = new CancellationTokenSource(); var token = _cts.Token; _currentTask = Task.Run(() => { _robotService.Run(selectedObject); }, token); } public void Stop() { if (_currentTask != null) { _robotService?.Stop(); if (!_currentTask.IsCompleted) { _cts?.Cancel(); } } } }
然后在DI中注册为Singleton:
services.AddSingleton<RobotTaskManager>();
修改ViewModel,注入该服务并委托任务控制逻辑:
private readonly RobotTaskManager _taskManager; public RobotViewModel(RobotTaskManager taskManager) { _taskManager = taskManager; } public void Start() { if (SelectedObject != null) { _taskManager.Start(SelectedObject); } } public void Stop() { _taskManager.Stop(); }
这种方式彻底解耦了ViewModel和任务状态,即使ViewModel被重新实例化,也能通过单例服务访问到正在运行的任务。
3. 使用ViewModel导航缓存
在导航框架中实现ViewModel缓存机制,例如在NavView_Navigate方法中,判断目标页面对应的ViewModel是否已存在,若存在则复用而非重新获取:
private readonly Dictionary<string, object> _viewModelCache = new(); private void NavView_Navigate(Type pageType, object? parameter = null) { var pageKey = pageType.FullName ?? pageType.Name; if (!_viewModelCache.TryGetValue(pageKey, out var viewModel)) { viewModel = App.Current.Services.GetService(pageType); _viewModelCache.Add(pageKey, viewModel); } var targetPage = App.Current.Services.GetService(pageType) as Page; if (targetPage != null) { targetPage.DataContext = viewModel; ContentFrame.Navigate(targetPage, parameter); } }
通过MainWindow维护的字典缓存已创建的ViewModel实例,确保导航时复用原有实例,保留状态引用。
内容的提问来源于stack exchange,提问作者kenneth

