C# 从后台线程引发的事件更新UI

C# 从后台线程引发的事件更新UI,c#,wpf,C#,Wpf,我的WPF应用程序在后台线程上启动长时间运行的函数,通常通过按钮单击/命令,例如 StartCommand = new RelayCommand(async o => await StartAsync(), o => true); ... private async Task StartAsync() { await Task.Run(() => LongRunningFunction()); } 这些长时间运行的函数引发各种事件,以向用户报告进度、更新UI值等。

我的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的常见异常(“调用线程无法访问此对象,因为其他线程拥有它”)),因此我必须将代码包装在
调度程序中。Invoke(…)
。我还没有真正发现我何时做或不必做这件事的模式,所以有人能解释一下吗

老实说,我很惊讶上面的代码居然能工作。断点确认这些事件处理程序在工作线程上运行,而不是在UI线程上运行,所以为什么我不一直看到异常?这与正在更新的属性类型有关吗

编辑人们正在为我已经意识到的一个问题提出答案。再读一遍我的问题,我可能会把读者和“大多数时候这很好,但偶尔我会得到通常的例外”这一部分混淆了。我不是说这是一个间歇性问题——我真正的意思是,我的一些虚拟机在更新各自进度事件处理程序中的绑定属性时没有问题,而其他虚拟机则有问题。我怀疑这与属性类型有关,例如,更新字符串属性可以工作,但不能更新ObservableCollection


我的问题更多的是好奇,即为什么一些绑定属性可以从b/g线程更新,而其他属性则不能。当您从后台线程更新UI时,您的代码应该总是抛出异常。您不一定总能看到异常,因为异常发生在后台线程中,并且仍然存在。对于此类例外情况

。。TPL需要支持这些异常,并保持这些异常,直到消费代码访问任务时可以再次抛出它们为止

因此,在从后台任务抛出异常后,您不会立即看到异常


更新

当从另一个线程访问控件时,可以了解一些常见的场景。 关于您描述的问题,它实际上取决于您绑定到的属性类型,如所述:

与基元类型的数据绑定基本上是类型安全的,因为绑定机制在内部使用dispatcher为您封送回UI线程

但是,您需要注意收集。例如,ObservableCollection不会处理此问题,因此您需要在UI线程上对集合进行更改


在这里,您可以找到更多详细信息。

您面临的问题是,您正在尝试更新位于不同线程中的UI元素,您可以尝试以下操作

 App.Current.Dispatcher.Invoke(new Action(() =>
            {
               // your code
            }));

但用户界面确实会更新。如果它默默地失败了,我希望这不会发生。从来没有发生过。甚至在1995年,当Windows的第一个多线程版本问世时也没有。您已经使用了
wait
,为什么要尝试从任务内部修改UI?如果您想向UI线程发送消息,请使用
IProgress
@AndrewStephens请检查更新的答案现在是否更有用。当您有
等待
时,绝对没有理由使用
BeginInvoke
。只需将UI更新与后台处理分离。在WPF中,无论如何都不应该直接修改UI,只应该修改ViewModel的属性。您可以使用
IProgress
从任何线程向视图发送消息。如果某些属性有问题,请发布代码和异常。ViewModel代码和数据绑定代码/XAML。发布完整异常,包括其调用堆栈。您可以使用
Exception.ToString()
获取它。它们都不应该有任何问题,因为它是读取绑定属性数据以响应
INotifyPropertyChanged
的UI线程。那次电话怎么会出现在另一条线上?没有代码和调用堆栈就无法应答为什么VM使用事件?谁调用了
LongRunningProcessProgressChanged
,为什么?为什么不使用
LongRunningProcessProgres(string)
方法?事件引入了紧密耦合,而
i进程
消除了这种耦合。在这种情况下,虽然没有要处理的事件,但您只想从同一个VM内部更新属性