ApartmentState for dummies
COM是.NET的祖先,COM具有很高的目标,其中之一是为类提供线程保证。COM类可以公开其对线程的要求,并且COM基础结构会确保满足这些要求。但是,在.NET中完全没有这一点。例如,您可以在多个线程中使用Queue<>对象,但如果不正确加锁,代码中会出现难以诊断的错误。创建COM对象的线程必须告诉COM它希望为具有受限线程选项的COM类提供什么样的支持。绝大多数这些类仅支持所谓的单线程公寓(STA)线程,其接口方法只能从创建实例的同一线程安全地调用。换句话说,它们宣布“我根本不支持线程,千万不要从错误的线程调用我”。即使客户端代码实际上从另一个线程调用它。STA和MTA是两种类型,它们在CoInitializeEx()调用中指定,任何与COM交互的线程都必须调用该函数。CLR在启动线程时会自动进行此调用。对于程序的主启动线程,它从Main()方法的[STAThread]或[MTAThread]属性中获取要传递的值,默认为MTA。对于您自己创建的线程,它由SetApartmentState()调用来确定,默认为MTA。线程池线程始终为MTA,无法更改。Windows中有很多需要STA的代码。您自己使用的显著示例包括剪贴板、拖放和shell对话框(如打开文件对话框)。还有很多您看不到的代码,比如UI自动化程序和用于观察消息的钩子。如果不知道该代码在哪个程序中使用,其作者将很难使其安全。因此,WPF或Windows Forms项目的UI线程必须始终为STA,创建窗口的任何线程也是如此。您向COM作出的承诺是您的线程是STA,然而,您需要遵循单线程公寓协议。这些要求相当严格,如果违反协议,可能会出现难以诊断的问题。要求是您不能在任何时间段阻塞线程,并且您必须运行消息循环。WPF或Winforms的UI线程满足后一个要求,但是如果创建自己的STA线程,您需要自行处理。违反协议的常见诊断是死锁。CLR内置了相当多的支持来满足这些要求,帮助您避免麻烦。锁定语句和WaitOne()方法在阻塞STA线程时会运行消息循环。然而,这仅满足了不阻塞的要求,您仍需要创建自己的消息循环。WPF和Winforms中的Application.Run()方法。此前我已经提供了一个包含更多关于消息循环对于保持COM正常运行的重要性的答案。您可以在这里找到该帖子。太棒了!我解决的错误是一个为长时间运行的报表生成器创建的线程,它使用WPF控件来构建报表的部分,所以这是有道理的,尽管我不知道该线程是否有消息循环。我确实不得不多次阅读MSDN帖子才能理解它,您的答案非常清晰和清楚地写出来。谢谢!