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
相关产品推荐
相关产品推荐

