让代码在WPF GUI线程中执行的首选方式是什么
本文关键字:方式 是什么 执行 代码 WPF GUI 线程 | 更新日期: 2023-09-27 18:21:59
我有一个主线程,其中包含我的WPF GUI和一个或多个后台线程,这些线程有时需要在主线程中异步执行代码(例如GUI中的状态更新)。
有两种方法(我知道,也许更多)可以实现这一点:
- 通过使用来自目标线程的同步上下文的
TaskScheduler
来调度任务,以及 - 通过使用来自目标线程的CCD_ 2来调用委托
代码中:
using System.Threading.Tasks;
using System.Threading;
Action mainAction = () => MessageBox.Show(string.Format("Hello from thread {0}", Thread.CurrentThread.ManagedThreadId));
Action backgroundAction;
// Execute in main thread directly (for verifiying the thread ID)
mainAction();
// Execute in main thread via TaskScheduler
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
backgroundAction = () => Task.Factory.StartNew(mainAction, CancellationToken.None, TaskCreationOptions.None, taskScheduler);
Task.Factory.StartNew(backgroundAction);
// Execute in main thread via Dispatcher
var dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
backgroundAction = () => dispatcher.BeginInvoke(mainAction);
Task.Factory.StartNew(backgroundAction);
我喜欢基于TPL的版本,因为我已经使用了很多TPL,它为我提供了很大的灵活性,例如,通过等待任务或将其与其他任务链接。然而,到目前为止,在我看到的大多数WPF代码示例中,都使用了Dispatcher变体。
假设我不需要任何灵活性,只想在目标线程中执行一些代码,那么人们更喜欢这种方式而不是另一种方式的原因是什么?是否存在任何性能影响?
我强烈建议您阅读基于任务的异步模式文档。这将允许您构建API,以便在async
和await
上市时做好准备。
我曾经使用TaskScheduler
来排队更新,类似于您的解决方案(博客文章),但我不再推荐这种方法。
TAP文档有一个简单的解决方案,可以更优雅地解决问题:如果后台操作想要发布进度报告,那么它需要IProgress<T>
:类型的参数
public interface IProgress<in T> { void Report(T value); }
那么提供一个基本实现就相对简单了:
public sealed class EventProgress<T> : IProgress<T>
{
private readonly SynchronizationContext syncContext;
public EventProgress()
{
this.syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
}
public event Action<T> Progress;
void IProgress<T>.Report(T value)
{
this.syncContext.Post(_ =>
{
if (this.Progress != null)
this.Progress(value);
}, null);
}
}
(SynchronizationContext.Current
本质上是TaskScheduler.FromCurrentSynchronizationContext
,而不需要实际的Task
s)。
Async CTP包含IProgress<T>
和类似于上面的EventProgress<T>
(但性能更高)的Progress<T>
类型。如果你不想安装CTP级别的东西,那么你可以使用上面的类型。
总之,实际上有四种选择:
IProgress<T>
-这是将来编写异步代码的方式。它还迫使您将后台操作逻辑与UI/ViewModel更新代码分离,这是一件好事TaskScheduler
——不错的方法;在切换到IProgress<T>
之前,我使用了很长一段时间。不过,它不会强制UI/ViewModel更新代码脱离后台操作逻辑SynchronizationContext
-与TaskScheduler
相同的优点和缺点,通过一个鲜为人知的APIDispatcher
-真的可以不推荐这个!考虑更新ViewModel的后台操作,因此进度更新代码中没有特定于UI的内容。在这种情况下,使用Dispatcher
只是将ViewModel绑定到UI平台。讨厌
附言:如果您确实选择使用Async CTP,那么我的Nito.AncEx库中还有一些额外的Dispatcher
0实现,其中包括一个(PropertyProgress
),它通过INotifyPropertyChanged
发送进度报告(在通过SynchronizationContext
切换回UI线程之后)。
我通常将Dispatcher
用于任何小型UI操作,并且只有在涉及大量繁重处理的情况下才使用Tasks。我还将TPL用于所有与UI无关的后台任务。
您可以使用Dispatcher
以不同的Dispatcher Priorities运行任务,这在使用UI时通常很有用,但我发现,如果后台任务涉及大量繁重的处理,它仍然会锁定UI线程,这就是为什么我不将其用于大型任务的原因。
我对所有繁重的操作都使用基于任务的,但如果需要更新GUI,我会使用调度器,否则会出现跨线程异常。据我所知,你必须使用调度器来更新GUI
更新:petebrown在他的博客文章中很好地涵盖了这一点http://10rem.net/blog/2010/04/23/essential-silverlight-and-wpf-skills-the-ui-thread-dispatchers-background-workers-and-async-network-programming