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

ViewModel中取消任务管理与Dispose阻塞等待问题解决

实现支持任务取消、重复调用与销毁等待的ViewModel

需求说明

  • 提供“即发即弃”的Load方法,通过后台线程刷新状态
  • Load方法中用Task执行耗时操作,通过ContinueWith切回UI线程更新ViewModel及视图状态
  • 重复调用Load时,若有任务正在执行则取消旧任务并创建新任务
  • 任务运行时通过IsLoading状态让UI显示加载中
  • ViewModel销毁时,必须阻塞等待所有未完成的OnLoadBackground任务执行完毕

遇到的问题

除最后一项外,其余需求均已实现,但出现以下问题:连续调用Load并取消任务后,调用Dispose时,Task.WaitAll(_tasks.Keys.ToArray())会抛出包含TaskCanceledException的AggregateException。实际需求是即使任务被取消,仍需等待OnLoadBackground执行完成。

尝试过移除ContinueWith改用Dispatcher调用,最终发现问题根源是等待了错误的任务:之前错误地等待了ContinueWith的延续任务,正确的做法应该等待后台执行的父任务。修改后问题解决,最终实现代码如下:

public class DocumentObjectViewModel2 : ObservableObject, IDisposable
{
    private readonly Dictionary<Task, CancellationTokenSource> _tasks = new();

    /// <summary>
    /// shows an indeterminate progress bar in the UI
    /// </summary>
    public bool IsLoading
    {
        get => _isLoading;
        set => SetProperty(ref _isLoading, value);
    }

    private bool _isLoading;

    public ICommand RefreshCMD { get; }

    public string Value
    {
        get => _value;
        set => SetProperty(ref _value, value);
    }

    private string _value = "Not Set";

    public DocumentObjectViewModel2()
    {
        RefreshCMD = new RelayCommand(Load);
    }

    /// <summary>
    /// This is only called from the UI thread
    /// </summary>
    public void Dispose()
    {
        // cancel all
        foreach (var load in _tasks)
        {
            Task t = load.Key;
            CancellationTokenSource c = load.Value;

            c.Cancel();
        }

        // wait for finish (this needs to block)
        Task.WaitAll(_tasks.Keys.ToArray());

        // dispose 
        foreach (var load in _tasks)
        {
            CancellationTokenSource c = load.Value;
            c.Dispose();
        }

        _tasks.Clear();
    }

    /// <summary>
    /// This is only called from the UI thread
    /// </summary>
    public void Load()
    {
        // cancel previous loads
        foreach (var load in _tasks.ToArray())
        {
            Task t = load.Key;
            CancellationTokenSource c = load.Value;
            c.Cancel();
        }

        CancellationTokenSource cancel = new();

        cancel.Token.Register(() =>
        {
            // task was canceled
            IsLoading = false;
        });

        IsLoading = true;

        Task<string?> task = Task.Run(async () =>
        {
            string? value = OnLoadBackground(cancel.Token);
            return value;

        }, cancel.Token);
            
        task.ContinueWith((x) =>
        {
            IsLoading = false;

            if (x.Status == TaskStatus.Faulted)
            {
                // report error
                return;
            }

            // completed.
            OnLoadUI(x.Result);
        },
        cancel.Token,
        TaskContinuationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());

        _tasks.Add(task, cancel);
    }

    private string? OnLoadBackground(CancellationToken cancellationToken)
    {
        if (cancellationToken.IsCancellationRequested)
            return "Canceled";

        Debug.WriteLine("Started...");

        // do work (passing in cancellationToken)
        Thread.Sleep(5000);

        Debug.WriteLine("Ended...");

        if (cancellationToken.IsCancellationRequested)
            return "Canceled";

        return $"{DateTime.Now}";
    }

    private void OnLoadUI(string? retrievedData)
    {
        Value = retrievedData ?? "NULL";
    }
}

内容的提问来源于stack exchange,提问作者Vastar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 15:40:17