不使用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写法:
- 保留
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时短暂显示,而非数据库检索期间。
- 将
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
相关产品推荐
相关产品推荐

