WPF中的Dispatcher和Thread之间的关系

25

我并不完全清楚一个应用程序中有多少个分发器(Dispatchers),以及它们与线程的关系或引用方式。

据我所知,WPF应用程序有2个线程(一个用于输入,另一个用于UI)和1个分发器(与UI线程相关联)。如果我创建了另一个线程 - 让我们称其为“工作线程” - 当我在工作线程上调用Dispatcher.CurrentDispatcher时,我会得到哪个分发器?

另一种情况: 假设有一个控制台应用程序,有2个线程 - 主线程和一个输入线程。在主线程上,我首先创建了输入线程,然后调用Application.Run()

Thread thread = new Thread(new ThreadStart(UserInputThreadFunction));
thread.Start();
Application.Run();

会有一个调度程序,对吧?在输入线程中,Dispatcher.CurrentDispatcher会返回主线程的调度程序吗?或者获取主线程调度程序实例的正确方式是什么?

在WPF应用程序中可能会有多个调度程序吗?是否存在任何情况下创建另一个调度程序是有意义的?

3个回答

32
WPF 应用程序有两个线程(一个用于输入,另一个用于 UI)。
这种说法并不完全正确。WPF 应用程序只有一个 UI 线程来处理所有 UI 交互和用户输入。还有一个“隐藏”的线程负责渲染,但通常开发人员不需要处理它。
Dispatcher / Thread 的关系是一对一的,即一个 Dispatcher 总是与一个线程相关联,并可用于将执行调度到该线程。`Dispatcher.CurrentDispatcher` 返回当前线程的 dispatcher,也就是说,当您在工作线程上调用 `Dispatcher.CurrentDispatcher` 时,您会得到该工作线程的 dispatcher。
Dispatcher 将按需创建,这意味着如果您访问 `Dispatcher.CurrentDispatcher` 并且当前线程没有与之关联的 dispatcher,则会创建一个 Dispatcher。
话虽如此,应用程序中的 dispatchers 数量总是小于或等于应用程序中的线程数。

那么,每个线程都在运行一个运行循环吗?因为性能原因,我几乎无法想象。 - j00hi
调度程序是按需创建的,即当您调用Dispatcher.CurrentDispatcher时,如果尚不存在,则会创建调度程序。 - Pavlo Glazkov
那么,当我在工作线程上调用 Dispatcher.CurrentDispatcher (并创建工作线程的分派程序),并在该工作线程上订阅事件时,是否必须调用 Dispatcher.Run() 来接收这些事件? - j00hi
@j00hi - 请参考 MSDN 文档中关于 Dispatcher.Run() 的说明:http://msdn.microsoft.com/zh-cn/library/system.windows.threading.dispatcher.run.aspx。基本上,是的,你需要调用 Run 方法来处理消息。 - Pavlo Glazkov
1
回复晚了,但你关于两个线程的说法是错误的: 一个用于渲染,另一个用于其他所有操作:输入、UI更新等。 它们被称为“渲染线程”和“UI线程”。 - Danahi
补充@Danahi的评论(是正确的),这个链接提供了更多信息:http://msdn.microsoft.com/en-us/magazine/cc163328.aspx - Jcl

12

WPF应用程序默认只有一个Dispatcher。 Dispatcher是唯一允许您与UI元素交互的线程。 它为您抽象了实现细节,因此您只需要担心是否在UI线程上,即Dispatcher。

如果您尝试直接从工作线程与可视化对象进行交互(例如,使用txtBkx.Text =“new”在文本框上设置文本),则必须切换到UI线程:

Application.Current.Dispatcher.Invoke(
    () => { txtBkx.Text = "new"; });

或者您可以使用SynchronizationContext.Current(在UI线程上)并使用其从不同的线程执行委托来在UI线程上执行操作。但您应该注意,Dispatcher.CurrentDispatcher可能并不总是设置的。

现在,实际上您可以在同一应用程序中创建不同的WPF窗口,并为每个窗口创建一个单独的调度程序:

Thread thread = new Thread(() =>
{
    Window1 w = new Window1();
    w.Show();

    w.Closed += (sender2, e2) =>
                w.Dispatcher.InvokeShutdown();

    System.Windows.Threading.Dispatcher.Run();
});

thread.SetApartmentState(ApartmentState.STA);
thread.Start();  

顺便提一下,在MVVM中,您可以从非UI线程更新模型并从非UI线程引发属性更改事件,因为WPF将为您调度PropertyChanged事件。但是,引发CollectionChanged必须在UI线程上进行。


1
这段代码 "Application.Current.Dispatcher..." 省了我好几个小时... 我一直想不通为什么它不起作用,直到看到你的帖子。现在我明白了,原来我一直在创建单独的 Dispatchers,而不是获取应用程序的主 Dispatcher... 谢谢! - Jason Stevenson
在调用关闭之后为什么还要调用运行? - StayOnTarget
@StayOnTarget 这只有在 Closed 事件执行时才运行,所以它不会在 Run 之前被调用(抱歉回复了一个旧评论)。 - NimbusHex
@NimbusHex 谢谢,实际上我不认为还有其他人回复。而且我完全忽略了 Shutdown 是在委托内部的,你说得对。 - StayOnTarget

3

Dispatcher(调度程序)始终与线程相关联,一个线程最多只能同时运行一个调度程序。线程不需要有调度程序。

默认情况下,只有一个调度程序 - 用于UI。有时候使用其他调度程序是有意义的,而有时候则不是。调度线程需要在Dispatcher.Run()方法中阻塞以便处理对调度程序的调用。像您的控制台输入线程这样的线程将无法处理调用。


我刚尝试了以下操作,它能够运行成功,但我并没有调用Dispatcher.Run(): Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Send, new WaitCallback(delegate(object state) { Console.WriteLine("Hello Dispatcher"); }), null);。或者它只能在相同的线程上调用并且如果在别的线程上调用就不能工作了? - j00hi
1
它能够工作是因为当前线程有一个正在运行的调度程序。WPF框架会自动为您设置这个。由于上面的代码片段在UI线程中执行,因此以下情况将发生。UI线程将从该方法返回 - 例如,您可能在按钮的事件处理程序中执行了该代码。之后,Dispatcher将看到有一个调用要处理并执行Console.WriteLine()调用。 - vidstige

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接