在vb.net WebService中查找内存泄漏时,我检测到阻塞的终结器,以及从未发布的几个对象(例如System.Threading.ReaderWriterLock)
Google告诉我这可能是因为STAThread属性是在我的main方法上设置的.我花了很长时间才发现VB.net使用STA作为默认值,而c#使用MTA.
当我将MTAThread-Attribute添加到我的Main方法时,一切正常,对象被释放.因此,如果我理解正确,则在STA模式中阻止Finalizer-Thread.
到目前为止一切顺利,但说实话,我今天第一次听说STA和MTA.可以在没有任何想法的情况下在STA和MTA之间切换吗?
更新 我仍然不确定我是否可以在不破坏我的代码的情况下在MTA和STA之间切换.这是一些更多的想法
因为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().消息循环实现了生产者 - 消费者问题的通用解决方案.需要能够将来自一个线程的调用编组到特定的其他线程.如果它没有泵,则在工作线程上进行的调用无法完成并将死锁.
不允许STA线程阻塞.阻塞大大增加了死锁的几率,被阻塞的线程不会消息.在.NET程序中较小的问题,CLR在WaitHandle.WaitOne()和Thread.Join()等调用上有很大的支持.
有时,COM组件本身会对由STA线程拥有的内容做出硬性假设.并在内部使用PostMessage(),通常用于引发事件.因此,即使您实际上从未对工作线程进行任何调用,该组件仍会出现故障.WebBrowser是最臭名昭着的例子,当线程不抽取时,它的DocumentCompleted事件不会触发.
您的网络服务无疑违反了第一颗子弹.您只能在Winforms或WPF应用程序中自动获得消息循环.是的,对终结器线程有害,因为它必须对COM对象的最终释放调用进行编组以保持对象的线程安全.由于STA线程没有泵送,因此死锁是不可避免的结果.一个很难诊断的问题,你得到的唯一提示就是程序的内存使用量会爆炸.
通过将线程标记为MTA,您明确承诺不为公寓线程的COM服务器提供安全的主页.COM现在被迫处理硬案件,它必须自己创建一个线程来提供安全性.那个线程总是抽水.虽然这可以解决您的Web服务器的问题,但应该注意,这不是灵丹妙药.那些额外的线程不是免费的,并且调用总是被编组,所以总是很慢.获取太多这些帮助程序线程是一个很难诊断的问题,唯一的提示是程序的内存使用情况爆炸:)
自动线程安全是一个非常好的功能.它有99%的时间没有任何麻烦.然而,摆脱1%的失败模式是一个非常头疼的问题.归根结底,它归结为普遍的事实,线程很复杂且容易出错.一种方法是不要将它留给COM,而是自己采取线索.这篇文章中的代码可能对此有所帮助.