我有一个主线程,其中包含我的WPF GUI和一个或多个后台线程,这些线程偶尔需要异步执行主线程中的代码(例如GUI中的状态更新)。
有两种方法(我知道,可能更多)来实现这一目标:
TaskScheduler
来安排任务,并Dispatcher
调用委托。在代码中:
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变体。
假设我不需要任何灵活性并且只想在目标线程中执行某些代码,那么一种方式优先于另一种方式的原因是什么?是否有任何性能影响?
答案 0 :(得分:9)
我强烈建议您阅读Task-based Asynchronous Pattern文档。这样,您就可以在async
和await
上街时准备好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级别的东西,那么您可以使用上面的类型。
总而言之,有四种选择:
IProgress<T>
- 这是将来编写异步代码的方式。它还会强制您将后台操作逻辑与 UI / ViewModel更新代码分开,这是一件好事。TaskScheduler
- 不是一个糟糕的方法;这是我在切换到IProgress<T>
之前很长一段时间使用的。但是,它不会强制UI / ViewModel更新代码超出后台操作逻辑。SynchronizationContext
- 与TaskScheduler
相同的优点和缺点,通过a lesser-known API。Dispatcher
- 真的可以不推荐这个!考虑更新ViewModel的后台操作 - 因此进度更新代码中没有特定于UI的操作。在这种情况下,使用Dispatcher
将您的ViewModel绑定到您的UI平台。讨厌。 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
中报道了这一点