.Net 2.0的Windows服务在垃圾回收期间挂起

5

我编写了一个 .Net (2.0) Windows 服务,用于通过串口连接向用户发送寻呼消息(通过第三方硬件)。

该服务周期性地查询数据库(OSI PI Historian),解析数据并根据解析的值决定是否发送消息。

服务使用查找对象(其值来自 SQL Server 数据库,并在每分钟刷新)获取用户地址值和要发送的消息字符串。

服务使用多个 Threading.Timers 触发定期的数据库调用和消息发送。

服务安装在安装有 .Net 2 SP2 的 Windows 2003 机器上。

服务正常运行约一周,然后挂起。日志(log4net)中没有记录异常。

我从服务器中进行了多次转储,它们都表现出相同的特征 - 一个线程触发 GC 但无限期挂起

06aaf06c 7c827b99 77e61d1e 00004acc 00000000 ntdll!KiFastSystemCallRet
06aaf070 77e61d1e 00004acc 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
06aaf0e0 79e8c5f9 00004acc ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac
06aaf124 79e8c52f 00004acc ffffffff 00000000 mscorwks!PEImage::LoadImage+0x1af
06aaf174 79e8c54e ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
06aaf188 79f2f88f ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x17
06aaf1a8 79f2f8ca 7a3b9020 00000000 ffffffff mscorwks!SVR::gc_heap::user_thread_wait+0x50
06aaf1b8 79f2f806 00000000 7a3b8b18 00080101 mscorwks!WKS::gc_heap::wait_to_proceed+0xe
06aaf1cc 79f92f5a 00000000 00000000 00000000 mscorwks!WKS::gc_heap::garbage_collect+0x247
06aaf1f8 79f94e26 00000000 00000000 00000030 mscorwks!WKS::GCHeap::GarbageCollectGeneration+0x1a9
06aaf284 79f926ce 052c75c8 00000030 00000000 mscorwks!WKS::gc_heap::try_allocate_more_space+0x15b
06aaf298 79f92769 052c75c8 00000030 00000000 mscorwks!WKS::gc_heap::allocate_more_space+0x11
06aaf2b8 79e73291 052c75c8 0000002e 00000000 mscorwks!WKS::GCHeap::Alloc+0x3b
06aaf2d4 79e7d8d4 0000002e 00000000 00000000 mscorwks!Alloc+0x60
06aaf310 79e99056 0000000f b456b75e 00001ce3 mscorwks!SlowAllocateString+0x29
06aaf3b4 792bb2c1 00000000 0108082b 00001f40 mscorwks!FramedAllocateString+0xa1
    Stack shortened for bravity...

托管堆栈:

ESP       EIP     
06aaf364 7c82847c [HelperMethodFrame: 06aaf364] 
06aaf3bc 792bb2c1 System.String.CreateStringFromEncoding(Byte*, Int32, System.Text.Encoding)
06aaf3dc 792aaf2a System.Text.EncodingNLS.GetString(Byte[], Int32, Int32)
06aaf3fc 6522d131 System.Data.SqlClient.TdsParserStateObject.ReadStringWithEncoding(Int32, System.Text.Encoding, Boolean)
06aaf41c 656ca93e System.Data.SqlClient.TdsParser.ReadSqlStringValue(System.Data.SqlClient.SqlBuffer, Byte, Int32, System.Text.Encoding, Boolean, System.Data.SqlClient.TdsParserStateObject)
06aaf448 6522d925 System.Data.SqlClient.TdsParser.ReadSqlValue(System.Data.SqlClient.SqlBuffer, System.Data.SqlClient.SqlMetaDataPriv, Int32, System.Data.SqlClient.TdsParserStateObject)
06aaf474 656208b7 System.Data.SqlClient.SqlDataReader.ReadColumnData()
06aaf484 65620962 System.Data.SqlClient.SqlDataReader.ReadColumn(Int32, Boolean)
06aaf4b4 65221415 System.Data.SqlClient.SqlDataReader.GetValueInternal(Int32)
06aaf4c8 652213af System.Data.SqlClient.SqlDataReader.GetValue(Int32)
06aaf4f8 65220aec System.Data.SqlClient.SqlDataReader.get_Item(System.String)
06aaf504 03616b81 AlarmEventCollator.DataAccess.GetAllAlarmDetails()
06aaf564 03616a28 AlarmEventCollator.AlarmBuilder.buildLookups()
06aaf590 0361aa89 AlarmEventCollator.AlarmBuilder.pollLookups(System.Object)
06aaf5d0 792a83ff System.Threading._TimerCallback.TimerCallback_Context(System.Object)
06aaf5d8 792e019f System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
06aaf5f0 792a836b System.Threading._TimerCallback.PerformTimerCallback(System.Object)
06aaf77c 79e71b4c [GCFrame: 06aaf77c] 

GC通常发生在数据库解析方法中。所有其他托管线程似乎都会挂起 - 这与GC过程一致。

我希望能得到任何指导。更多信息可根据请求提供。


你正在处理许多不同的连接(包括SQL和串行连接)。垃圾回收器是否能够收集所有旧项目,或者是否有一些东西被保留在内存中,会导致内存使用量增加(如果您不小心使用静态类或串行连接,则可能会出现此问题)?您是否观察过内存使用情况,以确保它不仅仅是因为GC无法运行而导致内存已满(这是一个愚蠢的问题,我知道,但我必须问)? - IAmTimCorey
1
此外,请务必确保在完成使用所有实现IDisposable的对象后及时处理它们。 - John Saunders
另一个有关于GC和多线程的线程可能会有所帮助:http://social.msdn.microsoft.com/forums/en-US/clr/thread/46e73747-98fb-4686-8e47-d2f679d67ba8 顺便问一下,您说的“线程触发GC”是什么意思?您是在显式调用GC还是GC是由“自然原因”触发的? - Adi
除了John说的之外,未托管的类应该定义终结器以便正确处理。 - Adi
@Paul,我建议你从查看终结器线程正在执行的内容开始调查。终结器会阻塞所有其他线程直到其完成,因此通过查看终结器停留的位置很可能找到有问题的对象。你可以使用“!threads”命令获取终结器线程的ID。 - seva titov
显示剩余4条评论
1个回答

2
在运行Server版本的垃圾回收(GC)的.NET进程中,在双核机器上有两个GC线程,每个处理器一个或者更确切地说是每个逻辑处理器一个。如果启用了超线程,则有4个GC线程。在运行工作站版本的GC的进程中,我们没有专用的GC线程,相反,垃圾回收运行在启动GC的线程上,因为当只有一个处理器/一个线程执行GC时,切换到不同线程进行垃圾回收是没有意义的。
Chris Lyon关于GC模式的文章写得很好,并且还有一篇有趣的文章介绍Orcas中引入的GC延迟模式。
这对你来说为什么重要?因为不同的GC模式针对不同的优化,您的内存使用、GC延迟等可能会因使用的GC模式而大不相同。例如,默认情况下,Windows服务获取工作站GC,但是如果有大量吞吐量(大量短生命周期分配),则为了内存使用和性能,最好使用服务器GC。

是的,我通过将GC模式设置为Server来解决了这个问题。似乎由于工作站模式下GC存在性能/可靠性问题。 - Paul Cassidy
如果这解决了问题,那么能否请您将其标记为答案并关闭。 - Ramprasad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接