我正在通过内存分析器运行我的应用程序以检查泄漏。事情似乎还算可以,但是我得到了很多这些OverlappedData
,它们似乎挂在终结器队列中无所作为。它们是被取消的重叠IO的结果,由于连接两端下层NetworkStream
的关闭而产生。
网络流本身已经被处理。没有任何实例的NetworkStream
存在。
通常,它们根源于被称为OverlappedDataCacheLine
的东西。我在回调函数中调用EndRead
作为我的第一件事,因此没有对应的BeginRead
调用不应该出现。
这是一个典型的工具呈现它的样子
最后,它会被GC,但要花费相当长的时间 - 大约半个小时才能杀死所有东西,当我启动了大约一千个流,将它们放入异步调用BeginRead
中并在大约一分钟后关闭它们。
这个程序在80端口上针对web服务器部分地重现了这个问题。任何Web服务器都可以做到。
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来收集这些东西。因此实际上似乎没有真正的问题,但仍然很想知道到底发生了什么以及为什么收集这个对象的速度比其他对象慢几倍,如果在以后出现碎片化内存等问题是否可能成为问题。
EndXxx
,考虑到除了回调之外没有什么东西在等待?我的意思是,如果事情出了差错,回调仍然会被调用,对吧? - Dervall