【问题标题】:Threads not being cleaned up after they finish execution线程完成执行后没有被清理
【发布时间】: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


【解决方案1】:

我的猜测是如果你像这样使用线程

new Thread(unused => HandleClientCommunication(clientConnection)).Start(); 
        }

如果您假设每秒有 100 次调用,您的应用程序中至少会多出 100 MB,因为每个线程至少需要 1MB,请记住垃圾收集器不能保证即使线程总是会收集内存已完成执行。
我在这里可以建议的是将所有代码切换为使用 Task。
你的代码看起来像这样

Task.Factory.StartNew(unused => HandleClientCommunication(clientConnection)); 
        }

这就是为什么你应该在线程上使用任务(如果可能的话)

  • 创建和销毁线程是一项昂贵的操作 时间
  • 拥有大量线程会浪费内存资源,并且还会损害性能 操作系统必须在可运行线程之间进行调度和上下文切换
  • 任务安排在线程池上。具体的线程数取决于使用的调度器。如果你的应用程序对线程池的请求很多,线程池会尝试为所有的线程池提供服务。 仅使用这一个线程的请求。但是,如果您的应用程序正在排队多个请求 比线程池线程处理它们的速度更快,将创建额外的线程。

【讨论】:

  • 试过了,现在线程对象的数量上升的速度不那么快了,但还是上升了。
  • 这是正常的,因为您创建了很多线程并且您无法控制,因此最好的方法是尝试在某个时间使用 Qeue 类来限制连接数例子
  • 恐怕不正常,线程数一直在增加,而且仍然很高,尽管所有当前连接都已关闭。
  • 多少个Requests多少个线程尝试做一些统计
  • 没有统计,我正在测试。每个请求都是我自己提出的请求。以前每个请求一个线程,现在有点模糊。看起来只要我不断提出一致的请求,线程数就不会增加。但是,如果我等几秒钟再做一个新的。客户端将挂起 1-2 秒。随后在服务器上创建一个新的线程对象,然后客户端得到响应。如果我在所有请求之间放置大约 4 秒,则每次都会创建一个新线程。如果请求之间的时间较短,则不会创建另一个线程对象
【解决方案2】:

所以我找到了答案,在注意到垃圾收集器在程序作为服务运行时无法运行,但在作为表单运行时确实运行(并解决了所有内存问题)。

删除 Main(string[] args) 处的 [STAThread] 修复了所有问题。代码本身没有任何问题。

http://support.microsoft.com/kb/828988

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-25
    • 1970-01-01
    相关资源
    最近更新 更多