获取代码在WPF GUI线程中执行的首选方法是什么?

时间:2012-01-30 18:36:00

标签: c# wpf task-parallel-library

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

有两种方法(我知道,可能更多)来实现这一目标:

  1. 通过使用目标线程的同步上下文中的TaskScheduler来安排任务,并
  2. 通过使用目标线程中的Dispatcher调用委托。
  3. 在代码中:

    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变体。

    假设我不需要任何灵活性并且只想在目标线程中执行某些代码,那么一种方式优先于另一种方式的原因是什么?是否有任何性能影响?

3 个答案:

答案 0 :(得分:9)

我强烈建议您阅读Task-based Asynchronous Pattern文档。这样,您就可以在asyncawait上街时准备好API。

我曾经使用TaskScheduler对更新进行排队,类似于您的解决方案(blog post),但我不再推荐这种方法。

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>Progress<T>类型,类似于上面的EventProgress<T>(但性能更高)。如果您不想安装CTP级别的东西,那么您可以使用上面的类型。

总而言之,有四种选择:

  1. IProgress<T> - 这是将来编写异步代码的方式。它还会强制您将后台操作逻辑 UI / ViewModel更新代码分开,这是一件好事。
  2. TaskScheduler - 不是一个糟糕的方法;这是我在切换到IProgress<T>之前很长一段时间使用的。但是,它不会强制UI / ViewModel更新代码超出后台操作逻辑。
  3. SynchronizationContext - 与TaskScheduler相同的优点和缺点,通过a lesser-known API
  4. Dispatcher - 真的可以推荐这个!考虑更新ViewModel的后台操作 - 因此进度更新代码中没有特定于UI的操作。在这种情况下,使用Dispatcher将您的ViewModel绑定到您的UI平台。讨厌。
  5. P.S。如果您确实选择使用Async CTP,那么我的Nito.AsyncEx library中还有一些IProgress<T>个实施,其中一个(PropertyProgress)通过INotifyPropertyChanged发送进度报告(通过SynchronizationContext切换回UI线程后)。

答案 1 :(得分:1)

我通常使用Dispatcher进行任何小型UI操作,如果涉及大量繁重的处理,则仅使用“任务”。我还将TPL用于与UI无关的所有后台任务。

您可以使用Dispatcher在不同的Dispatcher Priorities上运行任务,这在使用UI时通常很有用,但我发现如果有很多重的话,它仍会锁定UI线程后台任务中涉及的处理,这就是我不能将它用于大型任务的原因。

答案 2 :(得分:-1)

我使用基于所有繁重操作的任务,但如果我需要更新GUI,我使用调度程序,否则你将获得交叉线程异常。据我所知,您必须使用调度程序来更新GUI

更新:皮特·布朗在他的博文http://10rem.net/blog/2010/04/23/essential-silverlight-and-wpf-skills-the-ui-thread-dispatchers-background-workers-and-async-network-programming

中报道了这一点