什么是UI线程的定义?在.NET应用程序中只有一个UI线程吗?

18

什么是UI线程?.NET应用程序中是否只有一个UI线程?

3个回答

14

UI线程是单线程公寓线程,用于创建各种用户界面对象(在Winforms中,这意味着控件)。按照约定和规则,一个控件只能从创建它的线程中访问;否则可能会产生意想不到的结果,从视觉上的奇怪到崩溃。

除非你明确地创建更多,否则在Windows Forms应用程序中只有一个UI线程。虽然你可以创建另一个线程并启动消息循环,但很少有理由这样做,并且两个不同的UI线程无法“交流”,就像任何其他线程不能与UI线程交流一样。


2
+1 如果提到你可以这样做,但这通常不是应该采取的方式... - Reed Copsey
那么当您在STAThread内执行new Thread().Start()时会怎样呢? - user332724
@Griever:这将启动一个新的线程,但它与UI线程完全分离。在任何地方开始新的线程都会创建一个新的、不相关的线程。 - Reed Copsey
@Reed:你的意思是,如果我在Thread.Start内创建控件,那么这个控件就没有任何附加消息泵吗? - user332724
@Griever:是的。如果你试图将它添加到不同的表单中,你会得到错误。总的来说,这是一个不好的想法。(你需要将线程创建为STA线程,然后在线程中添加消息泵,然后创建你的表单。这样可以让你启动一个新的UI线程,但通常情况下这是一个不好的想法——因为很少有合法的理由这样做——因为你几乎总是最好将你的“工作”推到后台线程,并保留一个单一的UI线程) - Reed Copsey

10

UI线程有一些特殊的特点:

  • Windows为该线程关联了一个消息队列。这发生在线程上创建第一个窗口时。
  • 该线程运行一个消息循环,允许Windows将消息分派给窗口。调用Application.Run()方法会创建消息循环。
  • COM在该线程上初始化,并请求单线程单元(STA)。 STA对于许多Windows功能的正确工作是必需的,因为这些功能设计上不是线程安全的。COM确保以线程安全的方式调用这些功能,并根据需要从工作线程调度到STA线程进行调用。这些功能的示例包括拖放、剪贴板、shell对话框(例如OpenFileDialog)、WebBrowser等ActiveX控件,SetWindowsHookEx设置的窗口钩子,屏幕阅读器使用的辅助功能提供程序和UI自动化提供程序。所有外部代码都不是线程安全的。
  • 该线程永远不会在任何操作上阻塞,它始终响应,以便根据需要分派Windows消息,以保持用户界面响应性和COM调度请求流。例如,显式禁止对WaitHandle.WaitAny()进行调用并生成异常。CLR具有对WaitOne()和lock的特定支持,通过泵送内部消息循环来避免死锁。

进程的启动线程几乎总是被选为UI线程,尽管这不是硬性要求。 STA状态由Main()方法中的[STAThread]属性选择。

你可以通过确保满足上述要求来创建一个UI线程。在Winforms应用程序中,这可能看起来像这样:

    var ui = new Thread(() => { Application.Run(new Form2()); });
    ui.SetApartmentState(ApartmentState.STA);
    ui.Start();

这会创建第二个窗口,运行在自己的UI线程上。这种安排通常会遇到一个问题,您现在有两个单独的窗口,它们根本没有关联。第二个窗口不能被第一个窗口所拥有,它具有独立于第一个窗口的Z顺序。对于用户来说,这很难处理。SystemEvents.UserPreferenceChanged事件很显著,它必然会在错误的线程上触发其事件,这有可能导致死锁。许多WinForms控件都订阅了它。除了像启动画面这样的罕见情况外,这根本不会改善用户界面。


0

已编辑以确保正确性:

在 Windows Forms 中,每个活动应用程序都有一个 UI 线程,WPF 也有类似的概念。

例如:当您启动应用程序时,会有一个线程,当调用 Application.Run(new Form1()); 时,它就成为了 UI 线程。

如果您尝试在运行时执行 Application.Run(new Form2());,则会收到“System.InvalidOperationException: 在单个线程上启动第二个消息循环不是有效操作。请改用 Form.ShowDialog。”的错误提示。

如果您确实需要两个单独的窗体不共享同一线程,则需要创建一个新线程,然后调用 Application.Run(new MyForm()) 等等。但这并不常见。


1
这是不正确的。在Windows Forms中,不同的窗体都使用相同的线程。消息泵使它们都具有响应性。 - Reed Copsey
我把它搞反了,模态对话框需要一个新线程,而非模态对话框不需要 需要 新线程。但重点仍然在于,当从普通线程启动新窗体时,普通线程可以成为 UI 线程。 - David
这仍然是不正确的。在新线程中“启动表单”,如果没有特殊的关注来启动消息循环,将会导致各种问题,包括(在大多数情况下)由于默认缺少STA而导致崩溃。 - Reed Copsey
你可能是对的。过去曾经有这样一个情况,我试图在运行时显示一个表单,但出现了一个错误告诉我不能在同一UI线程上打开两个表单,我正在努力追踪并在此处进行评论,但我还没有找到它。 - David
刚刚编辑了我的帖子,我混淆了Application.Run和Form.Show。 - David

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