我正在通过内存分析器运行我的应用程序以检查泄漏。事情似乎还不错,但是我得到了很多 OverlappedData,它们似乎在终结器队列中徘徊,几乎什么也没做。它们是重叠 IO 的结果,该重叠 IO 已通过关闭NetworkStream
连接任一端的底层而被取消。
网络流本身被处理。没有NetworkStream
任何地方的实时实例。
通常,它们植根于一种叫做OverlappedDataCacheLine
.IEndRead
的东西,我做的第一件事就是在回调中调用,所以没有BeginRead
对应的EndRead
.
这是一个非常典型的外观,表明谁将其与工具隔离
最后,它确实得到了 GC,但它需要很长时间 - 当我开始大约一千个流时,大约需要半个小时才能杀死所有东西,将它们放入异步调用BeginRead
并在大约一分钟后关闭它们。
该程序在某种程度上针对端口 80 上的网络服务器重现了该问题。任何网络服务器都可以。
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;
class Program
{
static void Main(string[] args)
{
var clients = new List<TcpClient>();
for (int i = 0; i < 1000; ++i) {
var client = new TcpClient();
clients.Add(client);
client.BeginConnect("localhost", 80, connectResult =>
{
client.EndConnect(connectResult);
var stream = client.GetStream();
stream.BeginRead(new byte[1000], 0, 1000, result =>
{
try
{
stream.EndRead(result);
Console.WriteLine("Finished (should not happen)");
}
catch
{
// Expect to find an IO exception here
Console.WriteLine("Faulted");
}
}, stream);
}, client);
}
Thread.Sleep(10000); // Make sure everything has time to connect
foreach (var tcpClient in clients)
{
tcpClient.GetStream().Close();
tcpClient.Close();
}
clients.Clear(); // Make sure the entire list can be GC'd
Thread.Sleep(Timeout.Infinite); // Wait forever. See in profiler to see the overlapped IO take forever to go away
}
}
当然,这个程序不需要永远清理一千个OverlappedData
,因为它比真正的应用程序小得多,但它确实需要一段时间才能完成它。当我运行我的真实东西而不是这个测试应用程序时,我会收到关于卡住的终结器的警告。它在我的应用程序中没有做太多,只是尝试关闭可能尚未关闭的所有内容,并确保在任何地方都没有保留对任何内容的引用。
Dispose()
如果我打电话或Close()
在客户端上,它似乎根本不重要而且它是流。结果是一样的。
关于为什么会发生这种情况以及如何避免这种情况的任何线索?CLR 对我很聪明,并保持这些固定的内存块完好无损以准备新的调用?为什么终结器的完成速度如此之慢?
更新在通过在 F5 键上放一杯水并喝杯咖啡进行了一些非常愚蠢的负载测试之后,似乎有什么东西会在压力下触发更完整的 GC 来收集这些东西。所以实际上似乎没有真正的问题,但仍然很高兴知道这里实际发生了什么以及为什么收集这个对象比其他对象慢得多,如果这可能在后期阶段成为碎片化的问题记忆之类的。