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

不使用Async/Await实现WPF任务加载时UI响应与加载动画显示

问题描述

接手的WPF MVVM应用中,视图模型通过以下代码实现初始数据检索:

Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
                             .ContinueWith(Sub(t2 As Task(Of IEnumerable(Of Item)))
                                               For Each item As Item In t2.Result
                                                   _items.Add(New ItemViewModel(item))
                                               Next item
                                           End Sub, TaskScheduler.FromCurrentSynchronizationContext)

其中_items是绑定到ListView的ObservableCollection,当前运行正常。

现在需要在数据检索/刷新时显示加载动画:已在XAML中添加动画控件,将其Visibility绑定到视图模型的布尔属性ShowBusy,单独测试该属性切换显示/隐藏动画正常,但按以下方式编写代码时看不到动画:

Me.ShowBusy = True

' 上述数据检索代码

Me.ShowBusy = False

推测原因是数据检索代码未被await,主线程快速切换ShowBusy状态,UI来不及渲染动画。尝试过两种ContinueWith写法:

  1. 保留TaskScheduler.FromCurrentSynchronizationContext在原位置,追加ContinueWith设置ShowBusy=False:
Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
                             .ContinueWith(Sub(t2 As Task(Of IEnumerable(Of Item)))
                                               For Each item As Item In t2.Result
                                                   _items.Add(New ItemViewModel(item))
                                               Next item
                                           End Sub, TaskScheduler.FromCurrentSynchronizationContext) _
                             .ContinueWith(Sub() Me.ShowBusy = False)

结果动画仅在数据加载到ListView时短暂显示,而非数据库检索期间。

  1. 将TaskScheduler.FromCurrentSynchronizationContext移到最后设置ShowBusy=False的ContinueWith:
Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
                             .ContinueWith(Sub(t2 As Task(Of IEnumerable(Of Item)))
                                               For Each item As Item In t2.Result
                                                   _items.Add(New ItemViewModel(item))
                                               Next item
                                           End Sub) _
                             .ContinueWith(Sub() 
                                               Me.ShowBusy = False
                                           End Sub, TaskScheduler.FromCurrentSynchronizationContext)

结果触发异常:This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread。

需要一种无需大量使用Async/Await的简单解决方案。

解决方案

核心是确保ShowBusy=False在数据检索完成+UI线程更新集合完成之后执行,同时保证集合操作始终在UI线程进行。

正确的链式调用写法

Me.ShowBusy = True

Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
    ' 后台线程执行数据检索
    .ContinueWith(Sub(t2)
                      ' 切换到UI线程更新ObservableCollection,避免跨线程异常
                      If Not t2.IsFaulted Then
                          For Each item As Item In t2.Result
                              _items.Add(New ItemViewModel(item))
                          Next
                      End If
                  End Sub, TaskScheduler.FromCurrentSynchronizationContext) _
    ' UI线程更新完成后,关闭加载动画
    .ContinueWith(Sub(t3)
                      Me.ShowBusy = False
                  End Sub, TaskScheduler.FromCurrentSynchronizationContext)

关键说明

  • 第一个StartNew在后台线程跑GetItems,不会阻塞UI,此时ShowBusy=True已经让动画显示出来。
  • 第一个ContinueWith指定TaskScheduler.FromCurrentSynchronizationContext,强制集合更新操作在UI线程执行,解决跨线程异常问题。
  • 第二个ContinueWith同样绑定UI线程调度器,确保必须等集合更新完成后才关闭动画,这样动画会从数据开始检索时一直显示到UI更新完毕。

异常处理优化(可选)

如果GetItems可能抛出异常,建议在代码中加入异常处理,避免程序崩溃:

Me.ShowBusy = True

Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
    .ContinueWith(Sub(t2)
                      If t2.IsFaulted Then
                          ' 这里可以添加异常处理逻辑,比如记录日志、提示用户
                          ' 示例:Me.ErrorMessage = t2.Exception.InnerException.Message
                      Else
                          For Each item As Item In t2.Result
                              _items.Add(New ItemViewModel(item))
                          Next
                      End If
                  End Sub, TaskScheduler.FromCurrentSynchronizationContext) _
    .ContinueWith(Sub(t3)
                      Me.ShowBusy = False
                  End Sub, TaskScheduler.FromCurrentSynchronizationContext)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 01:03:16