.NET线程在';先发制人GC禁用';阻止GC并可能导致死锁的模式

.NET线程在';先发制人GC禁用';阻止GC并可能导致死锁的模式,.net,multithreading,performance,garbage-collection,clr,.net,Multithreading,Performance,Garbage Collection,Clr,[Edit0]People为优化实例创建(通过使用数组缓冲区)提供了一些很好的建议,从而降低了GC周期,但也请解释为什么只有此线程标记为“pre-emptive GC disabled”,我的案例是死锁吗 最近,我在用.NET 3.5编写的应用程序(在Windows XP上)上做一些测试,我注意到在某些情况下,应用程序的性能非常差,TaskManager中显示的内存是正常情况的倍,但没有挂起。我做了一个内存转储,并简单地将其放入其中,对于不太擅长WinDbg的人来说,这是一个很好的工具 我知道应

[Edit0]People为优化实例创建(通过使用数组缓冲区)提供了一些很好的建议,从而降低了GC周期,但也请解释为什么只有此线程标记为“pre-emptive GC disabled”,我的案例是死锁吗

最近,我在用.NET 3.5编写的应用程序(在Windows XP上)上做一些测试,我注意到在某些情况下,应用程序的性能非常差,TaskManager中显示的内存是正常情况的倍,但没有挂起。我做了一个内存转储,并简单地将其放入其中,对于不太擅长WinDbg的人来说,这是一个很好的工具

我知道应用程序中一定存在一些内存泄漏问题,但这里的问题是关于GC的

生成的报告中有一行:

线程6触发了垃圾回收。垃圾回收器线程只有在线程停止工作时才会开始工作 禁用先发制人GC已完成执行。以下线程禁用了先发制人GC:线程6

从描述中,我将其理解为GC和“先发制人禁用GC”线程的死锁(先发制人禁用GC”),然后我想知道为什么该线程标记为“先发制人禁用GC

然后我检查了线程6的调用堆栈并找到了源代码,它是一个显式创建的线程:

//Create the thread.
thread = new Thread(Execute) { Priority = ThreadPriority.Lowest };
thread.Start();
此线程所做的是线程安全地从队列中取消日志文本行的队列,并刷新到磁盘文件:

// Wait for an event from the thread that en-queue
waitEvent.WaitOne();  
if (!disposed)
{
    // Clear the entry queue.  
    bool queueEmpty;
    do
    { 
        // Pick the next logEntry in the queue safely in a locked way.
        LogEntry logEntry = null;
        lock (entryQueueLock)
        {
            logEntry = entryQueue.Dequeue();
        }

        var text = logEntry.Text;
        byte[] textBytes = Encoding.Default.GetBytes(text);
        fileStream.Write(textBytes, 0, textBytes.Length);
        fileStream.Flush();            
     }
     while (!queueEmpty);
}
线程6的调用堆栈在这一行停止,意味着这一行启动了GC:

byte[] textBytes = Encoding.Default.GetBytes(text);
然后请帮助解释为什么只有线程6(近50个线程)禁用了这种“先发制人GC””,以及如何避免这种情况:

这不是您的问题,但您可以通过以下方式显著减少线程生成的垃圾量:

如果你的应用程序必须转换大量数据,它应该重用输出缓冲区。在这种情况下,支持字节数组的
GetBytes
版本是最佳选择

您可以轻松做到这一点,如下所示:

/* helper extension method */
public static void Grow(ref byte[] buffer, int minSize)
{   if (minSize > buffer.Length) buffer = new byte[minSize];  }

/* outside the loop */
byte conversionBuffer[] = new byte[2048];
Encoding fileEncoding = Encoding.Default;

/* replace your GetBytes call with */
Grow(ref conversionBuffer, fileEncoding.GetMaxBytes(text));
int bytesUsed = fileEncoding.Get(text, 0, text.Count, conversionBuffer, 0);

/* and your Write call with */
fileStream.Write(conversionBuffer, 0, bytesUsed);

另外,我建议您使用a而不是手动锁定。

日志字符串都是纯英语单词,所以我真的需要编码吗?根据您的建议重新使用缓冲区,再加上这个代码更改:
byte[]bytes=newbyte[str.Length*sizeof(char)];System.Buffer.BlockCopy(str.ToCharArray(),0,bytes,0,bytes.Length)这是否更有用?@Shawn:不,这仍然会为每个项目创建一个全新的数组。它将生成一个UTF-16文件,这与您现在拥有的文件有很大不同。很抱歉,如果仍然使用缓冲区,代码将是这样的:
var minSize=text.Length;如果(minSize>conversionBuffer1.Length)conversionBuffer1=新字节[minSize];System.Buffer.BlockCopy(text.ToCharArray(),0,conversionBuffer1,0,minSize)我做了一些压力测试,注意到这段代码在计时方面可能有更好的性能(通过秒表,滴答声可能比您建议的要小得多),但在GC循环方面性能不好。顺便说一句,你知道那个僵局吗?或者这是一个僵局?为什么只有这个被标记为“先发制人GC禁用”的线程?@Shawn:我怀疑这实际上是一个死锁。尝试并观察“CollectionsDone”性能计数器(可能每一代都有一个),看看它是否被卡住了。我最初的想法是,这个特定的线程在运行GC的时候没有被监视,因为它是以协作方式运行GC的。无论如何,您的区块复制应该失败,因为每个
char
占用两个字节,而不是一个字节。或者更确切地说,它可能只复制了每条消息的一半,因为您指定了字节数以防止超出范围的访问。但是,这个答案不能回答为什么这个特定线程禁用了抢占。我有一个相关的问题,与编码无关,我得到了相同的线程场景和锁定。