我有一个包含我的 WPF GUI 的主线程和一个或多个偶尔需要在主线程中异步执行代码的后台线程(例如 GUI 中的状态更新)。
有两种方法(据我所知,也许更多)可以实现此目的:
- 通过使用来自目标线程的同步上下文的
TaskScheduler
安排任务,以及 - 通过使用来自目标线程的
Dispatcher
调用委托(delegate)。
在代码中:
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,它为我提供了很大的灵 active ,例如通过等待任务或将其与其他任务链接起来。但是,在我目前看到的大多数 WPF 代码示例中,都使用了 Dispatcher 变体。
假设我不需要任何这种灵 active ,只想在目标线程中执行一些代码,那么人们更喜欢一种方式而不是另一种方式的原因是什么?对性能有影响吗?
最佳答案
我强烈建议您阅读 Task-based Asynchronous Pattern文档。这将允许您构建 API 以在 async
时准备就绪。和 await
走上街头。
我曾经使用 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
。
异步 CTP 包含 IProgress<T>
和一个 Progress<T>
类似于 EventProgress<T>
的类型以上(但性能更高)。如果你不想安装 CTP 级别的东西,那么你可以使用上面的类型。
总而言之,实际上有四种选择:
-
IProgress<T>
- 这就是 future 异步代码的编写方式。它还迫使您将后台操作逻辑与UI/ViewModel 更新代码分开,这是一件好事。 -
TaskScheduler
- 不错的方法;这是我在切换到IProgress<T>
之前用了很长时间的东西.不过,它不会强制 UI/ViewModel 更新代码脱离后台操作逻辑。 -
SynchronizationContext
- 与TaskScheduler
相同的优点和缺点, 通过 a lesser-known API . -
Dispatcher
- 真的不能不推荐这个!考虑更新 ViewModel 的后台操作 - 因此进度更新代码中没有特定于 UI 的内容。在这种情况下,使用Dispatcher
只需将您的 ViewModel 绑定(bind)到您的 UI 平台。讨厌。
附言如果您确实选择使用异步 CTP,那么我还有一些额外的 IProgress<T>
我的 Nito.AsyncEx library 中的实现,包括通过 PropertyProgress
发送进度报告的 ( INotifyPropertyChanged
) (通过 SynchronizationContext
切换回 UI 线程后)。
关于c# - 让代码在 WPF GUI 线程中执行的首选方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9068931/