为什么在UI线程上调用thread. join()时不会挂起我的应用程序?
本文关键字:挂起 我的 应用程序 线程 UI 调用 join thread 为什么 | 更新日期: 2023-09-27 18:02:11
在我的理解中(我不擅长线程),Join()阻塞调用线程,直到Join()被调用的线程返回。
如果这是真的,并且Join()是从UI线程调用的,为一些长时间运行的操作创建新线程没有任何意义。在SO上有一些问题问为什么Join()挂起应用程序。在我看来很自然。
顺便说一下,即使它看起来很自然,我的应用程序也没有相应的行为。它不会挂起我的应用程序。没有挂起应用程序的线程代码:-
string retValue = "";
retValue = LongRunningHeavyFunction();
txtResult.Text = retValue;
不挂起应用的线程代码:-
string retValue = "";
Thread thread = new Thread(
() =>
{
retValue = LongRunningHeavyFunction();
});
thread.Start();
thread.Join();
txtResult.Text = retValue;
上面的代码在没有挂起应用程序的情况下工作得很好。函数调用大约需要15-20秒。为什么应用程序不挂起?这对我来说不是问题;实际上这是个好消息。但我就是不明白这有什么区别?这与我读到的和学到的不相符。
我正在使用DotNet Framework 4.0,如果这很重要。
程序的UI线程创建了一个STA,一个单线程公寓,这要归功于Main()入口点上的[STAThread]
属性。还要查看Thread.SetApartmentState()。STA对非线程安全的代码非常友好,这些代码通常是你无法看到的,因为它是另一个程序的一部分。
就像在剪贴板上放置数据的代码一样,在拖放操作中提供数据,当你显示OpenFiledialog时被激活的shell扩展,像WebBrowser这样的控件需要客户端代码有一个调度程序,这样它就可以引发DocumentCompleted事件,监听通知的窗口钩子,依赖于可访问性api来实现UI自动化的代码或屏幕阅读器。等等。
这些代码都不要求是线程安全的,即使涉及到不同进程中的多个线程。编写线程安全的代码通常是困难的,它变得异常困难,因为作者没有任何好的方法来测试它。他不可能对所有可能激活他代码的程序进行测试。公寓就是为了解决这个问题而发明的,它消除了编写线程安全代码的需要。
STA是你做出的承诺。你发誓,发誓你的UI线程行为良好,它必须有一个调度程序(又名泵消息循环),并且永远不会阻塞。dispatcher以线程安全的方式运行代码是必要的,它是生产者-消费者问题的通用解决方案。而且您绝对不能阻塞,因为这样做很可能在其他程序员的代码中导致死锁。这种死锁永远无法调试,因为您没有该代码的源代码。Winforms、WPF或Modern UI程序的UI线程总是实现这个承诺,这就是UI线程不同于你可能使用的其他线程的基本原因。
足够的介绍,Thread.Join()调用阻塞,从而违反STA契约。这是非常糟糕的,所以CLR设计者对此做了一些事情。正如你所发现的,他们通过而不是阻塞来实现Join(),在STA线程上调用它。
他们通过实现Application.DoEvents()来实现这一点。这是一个非常臭名昭著的方法来避免UI线程变得无响应。这可能导致很难诊断重入错误。它们的版本不像DoEvents()那样糟糕,它对允许发送哪种消息非常有选择性,以最小化风险。
通常可以解决问题,但是绝不是您想要故意测试自己的。可重入性bug是非常糟糕的bug,就像线程赛跑bug一样糟糕。因此,你不应该在UI线程上使用thread. join()。. net框架通过BackgroundWorker和Task类提供了很好的替代方案,它们都为您提供了在线程完成时执行代码(无论在Join()调用之后的是什么)的非常好的方法。