让代码在WPF GUI线程中执行的首选方式是什么

本文关键字:方式 是什么 执行 代码 WPF GUI 线程 | 更新日期: 2023-09-27 18:21:59

我有一个主线程,其中包含我的WPF GUI和一个或多个后台线程,这些线程有时需要在主线程中异步执行代码(例如GUI中的状态更新)。

有两种方法(我知道,也许更多)可以实现这一点:

  1. 通过使用来自目标线程的同步上下文的TaskScheduler来调度任务,以及
  2. 通过使用来自目标线程的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变体。

假设我不需要任何灵活性,只想在目标线程中执行一些代码,那么人们更喜欢这种方式而不是另一种方式的原因是什么?是否存在任何性能影响?

让代码在WPF GUI线程中执行的首选方式是什么

我强烈建议您阅读基于任务的异步模式文档。这将允许您构建API,以便在asyncawait上市时做好准备。

我曾经使用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,而不需要实际的Tasks)。

Async CTP包含IProgress<T>和类似于上面的EventProgress<T>(但性能更高)的Progress<T>类型。如果你不想安装CTP级别的东西,那么你可以使用上面的类型。

总之,实际上有四种选择:

  1. IProgress<T>-这是将来编写异步代码的方式。它还迫使您将后台操作逻辑UI/ViewModel更新代码分离,这是一件好事
  2. TaskScheduler——不错的方法;在切换到IProgress<T>之前,我使用了很长一段时间。不过,它不会强制UI/ViewModel更新代码脱离后台操作逻辑
  3. SynchronizationContext-与TaskScheduler相同的优点和缺点,通过一个鲜为人知的API
  4. Dispatcher-真的可以推荐这个!考虑更新ViewModel的后台操作,因此进度更新代码中没有特定于UI的内容。在这种情况下,使用Dispatcher只是将ViewModel绑定到UI平台。讨厌

附言:如果您确实选择使用Async CTP,那么我的Nito.AncEx库中还有一些额外的Dispatcher0实现,其中包括一个(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