3

在寻找 vb.net WebService 中的内存泄漏时,我检测到终结器被阻塞,以及一些从未释放的对象(例如 System.Threading.ReaderWriterLock)

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

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

到目前为止一切都很好,但老实说,我今天第一次听说 STA 和 MTA。我可以不加思索地在 STA 和 MTA 之间切换吗?

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

  • 我不在我的代码中使用 COM 对象。
  • 但是我正在使用的其他一些库似乎在后台使用它们,例如 OracleCommand
  • 我的应用程序是用 vb.net 编写的,因此偶然将其设置为 STA-Appartment,因为这是 vb.net 默认设置,我在开发时并不知道
  • 如果我用 c# 编写我的应用程序,它会默认设置为 MTA
  • 那么我是否需要关心在后台使用的 COM 对象?
4

1 回答 1

9

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

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

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 事件不会在线程不启动时触发。

您的 Web 服务无疑违反了第一条。您只能在 Winforms 或 WPF 应用程序中自动获得消息循环。是的,终结器线程中毒了,因为它对 COM 对象的最终释放调用必须被编组以保持对象线程安全。死锁是不可避免的结果,因为 STA 线程没有抽水。一个很难诊断的棘手问题,您得到的唯一提示是程序的内存使用量激增。

通过将线程标记为 MTA,您明确承诺不会为单元线程 COM 服务器提供安全的家。COM现在被迫处理hard case,它必须自己创建一个线程来提供安全性。那个线程总是泵。虽然这可以解决您的 Web 服务器的问题,但应该注意这不是万能的。那些额外的线程不是免费的,而且调用总是被编组的,所以总是很慢。获得太多这些帮助线程是一个很难诊断的棘手问题,您得到的唯一提示是程序的内存使用量爆炸式增长:)

自动线程安全是一个非常好的特性。它在 99% 的时间内都可以正常工作,没有任何麻烦。然而,摆脱 1% 的故障模式是一个非常令人头疼的问题。归根结底,它归结为普遍真理,线程复杂且容易出错。一种方法是不要把它留给 COM,而是自己亲自动手。这篇文章中的代码可能对此有所帮助。

于 2014-08-07T17:26:43.630 回答