【发布时间】:2014-05-29 13:05:31
【问题描述】:
异步多线程 TCP 服务器的简单实现。 按预期工作,但内存使用量不断上升。 经过调查,我发现该内存都是未清理的线程对象。 但是,线程确实将“主机断开连接”写入日志文件,并且由于这是该线程执行的最后一行代码,我希望它能够自行清理。但这似乎并没有发生。对于建立的每个连接,都会创建一个线程并停止运行,但它从未完全清理过。
发生了什么事?
也没有产生异常。
private void AcceptNextClient()
{
if (acceptConnections) serverSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), serverSocket);
}
private void AcceptTcpClientCallback(IAsyncResult ar)
{
try
{
TcpListener serverSocket = (TcpListener)ar.AsyncState;
TcpClient clientConnection = serverSocket.EndAcceptTcpClient(ar);
new Thread(unused => HandleClientCommunication(clientConnection)).Start();
}
catch (Exception ex) { Disk.AppendLog(ex.ToString()); }
AcceptNextClient();
}
private void HandleClientCommunication(TcpClient tcpClient)
{
string hostName = "";
try
{
using (StreamWriter sw = new StreamWriter(tcpClient.GetStream()))
using (StreamReader sr = new StreamReader(tcpClient.GetStream()))
{
hostName = Dns.GetHostEntry(((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address).HostName;
bool read = true;
while (read)
{
string buffer = sr.ReadLine();
if (buffer == null) read = false;
else
{
if (buffer.ToUpper().Equals("CLOSE_CONNECTION")) read = false;
else
{
sw.WriteLine(buffer);
sw.Flush();
}
}
}
tcpClient.Close();
}
}
catch (Exception ex)
{
Disk.AppendLog(hostName + " " + ex.ToString());
}
Disk.AppendLog("host disconnected");
}
public static void AppendLog(string msg)
{
File.AppendAllText(exePath + "errors.log", DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString() + " " + msg + Environment.NewLine);
}
【问题讨论】:
-
" 一个线程被创建并停止运行,但它从未完全清理干净。"是什么让你得出这样的结论?并说“因为内存增加了”。您是否尝试过内存分析器?
-
是的,我尝试了 JetBrains .dotTrace,并且进程资源管理器有一个关于 .NET 性能的选项卡,.NET CLR LocksAndThreads 在其中我可以清楚地看到客户端连接时线程对象的数量正在上升。当他们断开连接时,永远不会退缩。首次启动该程序时它大约为 7MB,但几天后它超过 100MB 并且仍在增长。执行 GC.Collect() 也不会清理它们。
-
如果已写入“主机断开连接”,则没有任何迹象表明可以保留线程。我的怀疑是,根本没有任何记忆压力值得做一个完整的收集。您是否尝试强制使用
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers();?注意:你不应该把它留在生产代码中——它只是为了帮助确定这是否是原因。 -
要检查的另一件事:线程是否从
Disk.AppendLog返回? -
当我将它作为服务运行时,GC 方法都不会清理线程。我正在 GC 之前和之后记录一条消息。如果我只做 GC.Collect(),在被记录之前和之后。如果我用 waitforfinalizers 做另一个,第二个永远不会被记录。但是,如果我不将其作为服务运行,则两种 GC 收集方法都可以完美运行,同时还可以清理线程对象。这到底是怎么回事? @Marc Gravell
标签: c# multithreading asynchronous tcplistener