You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

导航重载ViewModel时如何保留Task对象引用?

导航重载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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 16:02:10