我的WPF应用程序在后台线程上启动长时间运行的功能,通常是通过按钮点击/命令,例如。
StartCommand = new RelayCommand(async o => await StartAsync(), o => true);
...
private async Task StartAsync()
{
await Task.Run(() => LongRunningFunction());
}
这些长时间运行的函数引发各种事件以向用户报告进度,更新UI值等。视图模型处理此类事件并更新绑定属性,例如:
private void LongRunningProcessProgressChanged(object sender, ProgressEventArgs e)
{
StatusMessageText = string.Format("Progress {0}%", e.Progress);
}
大部分时间这种方法都可以正常工作,但偶尔我会得到关于从后台线程更新UI的常见例外("调用线程无法访问此对象,因为不同的线程拥有它")),所以我必须将代码包装在Dispatcher.Invoke(...)
中。我还没有真正发现过一个关于我什么时候做或者不必这样做的模式,所以有人可以对它有所了解吗?
说实话,我对以上代码的工作原理感到惊讶。断点确认这些事件处理程序在工作线程上运行,而不是UI线程,那么为什么我不能一直看到异常呢?是否与正在更新的财产类型有关?
编辑人们正在提出我已经知道的问题的答案。再次阅读我的问题我可能会把读者与部分混淆#34;大部分时间这都很好但偶尔会得到通常的例外情况#34;我并不是说这是一个间歇性的问题 - 我真正的意思是我的一些虚拟机在各自的进度事件处理程序中更新绑定属性时遇到问题,而其他虚拟机则这样做。我怀疑它与属性类型有关,例如更新字符串属性有效但不是ObservableCollection。
我的问题更多的是一个好奇的事情,即为什么一些绑定的属性可以从b / g线程更新,而其他人不是。
答案 0 :(得分:2)
当您从后台线程更新UI时,您的代码应始终抛出异常。您并不总是看到异常,因为异常发生在后台线程中并保持unobserved。对于此类例外
.. TPL需要支持这些异常,并坚持使用它们直到消费代码访问任务时再次抛出它们。
因此,在从后台任务中抛出异常后,您根本看不到异常。
<小时/> 更新
This answer揭示了从另一个线程访问控件时的常见场景。 关于您描述的问题,它实际上取决于您绑定的属性类型,如MSDN forum所述:
对基本类型的数据绑定基本上是类型安全的,因为绑定机制在内部使用调度程序为您编组返回UI线程。
但是,您需要注意收藏。例如,ObservableCollection将不处理此问题,因此您需要在UI线程上对集合进行更改。
在此blog post中,您可以找到更多详细信息。
答案 1 :(得分:-1)
您面临的问题是,您正在尝试更新位于不同线程中的UI元素,您可以尝试使用
App.Current.Dispatcher.Invoke(new Action(() =>
{
// your code
}));