我有一个主线程,其中包含我的 WPF GUI 和一个或多个后台线程,这些线程偶尔需要在主线程中异步执行代码(例如 GUI 中的状态更新)。
有两种方法(据我所知,也许更多)可以实现此目的:
- 通过使用目标线程同步上下文中的 TaskScheduler 来调度任务,以及
- 使用 Dispatcher 调用委托/code> 来自目标线程。
在代码中:
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 变体。
假设我不需要任何灵活性,只想在目标线程中执行一些代码,那么人们更喜欢一种方式而不是另一种方式的原因是什么?对性能有影响吗?
I have a main thread that contains my WPF GUI and one or more background threads that occassionally need to execute code in the main thread asynchronously (e.g. a status update in the GUI).
There are two ways (that I know of, maybe more) to accomplish this:
- by scheduling a task using the
TaskScheduler
from the target thread's synchronization context, and
- by invoking a delegate using the
Dispatcher
from the target thread.
In code:
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);
I like the TPL-based version, because I already use TPL a lot and it provides me with a lot of flexibility, e.g. by waiting on the task or chaining it with other tasks. However, in most examples of WPF code I have seen so far, the Dispatcher variant was used.
Assuming I didn't need any of that flexibility and only wanted to execute some code in the target thread, what are the reasons one would prefer one way over the other? Are there any performance implications?
发布评论
评论(3)
我强烈建议您阅读基于任务的异步模式文档。这将允许您构建 API,以便在
async
和await
出现时做好准备。我曾经使用
TaskScheduler
来排队更新,类似于您的解决方案(博客文章),但我不再推荐这种方法。TAP 文档有一个简单的解决方案,可以更优雅地解决问题:如果后台操作想要发出进度报告,那么它需要一个
IProgress
类型的参数:然后相对简单地提供一个基本实现:(
SynchronizationContext.Current
本质上是TaskScheduler.FromCurrentSynchronizationContext
,不需要实际的任务
)。异步 CTP 包含
IProgress
和Progress
类型,该类型类似于上面的EventProgress
(但性能更高) )。如果你不想安装CTP级别的东西,那么你可以只使用上面的类型。总而言之,实际上有四种选择:
IProgress
- 这是将来编写异步代码的方式。它还迫使您将后台操作逻辑与UI/ViewModel更新代码分开,这是一件好事。TaskScheduler
- 不错的方法;在切换到IProgress
之前,我使用了很长一段时间。不过,它不会强制 UI/ViewModel 更新代码脱离后台操作逻辑。SynchronizationContext
- 与TaskScheduler
相同的优点和缺点,来自 一个鲜为人知的 API。Dispatcher
- 真的不能不推荐这个!考虑更新 ViewModel 的后台操作 - 因此进度更新代码中没有任何特定于 UI 的内容。在这种情况下,使用Dispatcher
只是将您的 ViewModel 绑定到您的 UI 平台。可恶的。PS 如果您确实选择使用异步 CTP,那么我的 Nito 中还有一些额外的
IProgress
实现。 AsyncEx 库,包括一个 (PropertyProgress
),它通过INotifyPropertyChanged
发送进度报告(通过切换回 UI 线程后)SynchronizationContext
)。I highly recommend that you read the Task-based Asynchronous Pattern document. This will allow you to structure your APIs to be ready when
async
andawait
hit the streets.I used to use
TaskScheduler
to queue updates, similar to your solution (blog post), but I no longer recommend that approach.The TAP document has a simple solution that solves the problem more elegantly: if a background operation wants to issue progress reports, then it takes an argument of type
IProgress<T>
:It's then relatively simple to provide a basic implementation:
(
SynchronizationContext.Current
is essentiallyTaskScheduler.FromCurrentSynchronizationContext
without the need for actualTask
s).The Async CTP contains
IProgress<T>
and aProgress<T>
type that is similar to theEventProgress<T>
above (but more performant). If you don't want to install CTP-level stuff, then you can just use the types above.To summarize, there are really four options:
IProgress<T>
- this is the way asynchronous code in the future will be written. It also forces you to separate your background operation logic from your UI/ViewModel update code, which is a Good Thing.TaskScheduler
- not a bad approach; it's what I used for a long time before switching toIProgress<T>
. It doesn't force the UI/ViewModel update code out of the background operation logic, though.SynchronizationContext
- same advantages and disadvantages toTaskScheduler
, via a lesser-known API.Dispatcher
- really can not recommend this! Consider background operations updating a ViewModel - so there's nothing UI-specific in the progress update code. In this case, usingDispatcher
just tied your ViewModel to your UI platform. Nasty.P.S. If you do choose to use the Async CTP, then I have a few additional
IProgress<T>
implementations in my Nito.AsyncEx library, including one (PropertyProgress
) that sends the progress reports throughINotifyPropertyChanged
(after switching back to the UI thread viaSynchronizationContext
).我通常使用
Dispatcher
来执行任何小型 UI 操作,并且仅在涉及大量繁重处理时才使用任务。我还将 TPL 用于与 UI 无关的所有后台任务。您可以使用
Dispatcher
在不同的调度程序优先级,在使用 UI 时通常很有用,但是我发现如果后台任务涉及大量繁重处理,它仍然会锁定 UI 线程,这就是我不这样做的原因't将其用于大型任务。I usually use the
Dispatcher
for any small UI operations, and only use Tasks if a lot of heavy processing is involved. I also use TPL for all background tasks that are not UI-related.You can use the
Dispatcher
to run tasks at different Dispatcher Priorities, which is often useful when working with the UI, however I find it still locks up the UI thread if there is a lot of heavy processing involved in the background task, which is why I don't use it for large tasks.我对所有繁重的操作使用基于任务的操作,但如果我需要更新 GUI,我会使用调度程序,否则您将遇到跨线程异常。据我所知,您必须使用调度程序来更新 GUI
更新:pete Brown 在他的博客文章 http://10rem.net/blog/2010/04/23/essential-silverlight-and-wpf-skills-the-ui-thread-dispatchers-background-workers-and-async-network-编程
I use task based for all heavy operations, but if I need to update the GUI I use the dispatcher otherwise you'll get cross threading exceptions. As far as I'm aware you have to use the dispatcher to update the GUI
update: pete brown covers it well in his blog post http://10rem.net/blog/2010/04/23/essential-silverlight-and-wpf-skills-the-ui-thread-dispatchers-background-workers-and-async-network-programming