在STAThread和MTAThread之间切换以及内存泄漏

时间:2014-08-07 12:48:16

标签: c# vb.net sta mta

在vb.net WebService中查找内存泄漏时,我检测到阻塞的终结器,以及从未发布的几个对象(例如System.Threading.ReaderWriterLock)

谷歌告诉我这可能是,因为STAThread属性是在我的main方法上设置的。 花了很长时间才发现VB.net使用STA作为默认值,而c#使用MTA。

当我将MTAThread-Attribute添加到我的Main方法时,一切正常,对象被释放。 因此,如果我理解正确,则在STA模式中阻止Finalizer-Thread。

到目前为止一切顺利,但说实话,我今天第一次听说STA和MTA。 我可以在没有任何想法的情况下在STA和MTA之间切换吗?

更新 我还不确定我是否可以在不破坏我的代码的情况下在MTA和STA之间切换。 这是一些更多的想法

  • 我的代码中没有使用COM对象。
  • 但我使用的其他一些图书馆似乎在幕后使用它们,例如OracleCommand
  • 我的应用程序是用vb.net编写的,所以很有可能它被设置为STA-Appartment,因为这是vb.net默认值,我在开发时不知道
  • 如果我在c#中编写了我的应用程序,默认情况下会将其设置为MTA
  • 那么我是否需要关心引擎盖下使用的COM对象?

1 个答案:

答案 0 :(得分:7)

  

因为STAThread属性是在我的main方法

上设置的

是的,这是VB.NET从VB6继承的令人遗憾的做法。 COM中的一个强大目标(VB6的原始基础以及您在Web服务中使用的内容)是隐藏线程的复杂性并自动处理线程不安全的代码,而客户端程序员不必了解它。 COM对象告诉COM运行时它支持哪种线程。到目前为止,最常见的选择是“公寓”,这是一个令人费解的词,意味着它不是线程安全的。

COM通过自动将COM方法的调用从工作线程封送到创建COM对象的线程来解决线程安全问题。从而保证COM对象的线程安全性。 .NET中的等价物是Dispatcher.Invoke()或Control.Invoke()。您必须在.NET程序中显式调用以保持线程不安全的用户界面正常工作的方法,它完全自动完成COM对象。

这种封送非常昂贵,它不可避免地涉及两个线程上下文切换以及序列化方法参数的开销,至少有数万个CPU周期。

一个线程可以告诉COM它是一个友好的家庭,对于一个线程不安全的COM对象,并将处理编组要求,它标志着自己是一个单线程公寓。 STA。它对COM方法的任何调用都不必编组并以全速运行。如果从工作线程进行调用,则STA线程负责实际进行调用。

然而,STA线程必须遵守两个非常重要的规则。破坏其中一条规则会导致很难诊断运行时故障。如果您违反这些规则就会出现死锁,就像您在终结器线程中观察到的那样。他们是:

  • STA线程必须引发消息循环。相当于.NET程序中的Application.Run()。消息循环实现了producer-consumer problem的通用解决方案。必须能够将来自一个线程的调用编组到特定的其他线程。如果它没有泵,那么在工作线程上进行的调用无法完成并将死锁。

  • 不允许STA线程阻止。阻塞大大增加了死锁的几率,被阻塞的线程不会消息。在.NET程序中较小的问题,CLR在WaitHandle.WaitOne()和Thread.Join()等调用上有很大的支持。

  • 有时COM组件本身会对由STA线程拥有的内容做出硬性假设。并在内部使用PostMessage(),通常用于引发事件。因此,即使您实际上从未对工作线程进行任何调用,该组件仍会出现故障。 WebBrowser是最臭名昭着的例子,它的DocumentCompleted事件在线程不泵时不会触发。

您的网络服务无疑违反了第一颗子弹。您只能在Winforms或WPF应用程序中自动获得消息循环。是的,终结器线程中毒,因为它对COM对象的最终释放调用必须被编组以保持对象线程安全。由于STA线程没有泵送,因此死锁是不可避免的结果。一个很难诊断的问题,你得到的唯一暗示就是程序的内存使用量会爆炸。

通过将线程标记为MTA,您明确承诺为公寓线程的COM服务器提供安全的家。 COM现在被迫处理硬件案例,必须自己创建一个线程以提供安全性。那个线程总是抽水。虽然这可以解决您的Web服务器的问题,但应该注意,这是的万能药。那些额外的线程不是免费提供的,而且始终调用的调用总是很慢。获取太多这些帮助程序线程是一个很难诊断的问题,唯一的提示是程序的内存使用情况爆炸:)

自动线程安全是一个非常好的功能。它有99%的时间没有任何麻烦。然而,摆脱1%的失败模式是一个非常头疼的问题。归根结底,它归结为普遍的事实,线程很复杂且容易出错。一种方法是不要将它留给COM,而是自己采取线索。 this post中的代码可能对此有所帮助。