You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#多线程TcpClient场景调用Application.Exit()无法正常关闭连接问题

问题根因

你碰到的System.Net.Sockets.SocketException (0x80004005): A blocking operation was interrupted by a call to WSACancelBlockingCall不是代码bug,是TcpListener.Stop()的预期行为:AcceptTcpClient()是阻塞方法,当监听Socket被主动关闭时,阻塞在这个调用上的线程会被系统强制唤醒,抛出该异常。你之前没有对这个场景做特殊处理,才会导致退出流程卡住。
另外原代码没有统一管理所有已接入的TcpClient实例,停止监听时没有主动断开现有设备连接,加上手动启动的工作线程默认是前台线程(只要有前台线程未终止,进程就不会退出),才会出现窗体关闭后进程在后台残留的问题。你之前写的CancellationToken逻辑完全无效——StartServer()一旦进入AcceptTcpClient()阻塞状态,根本走不到外层的IsCancellationRequested判断,等于无效代码。

修复步骤
  • 新增线程安全的集合,统一存储所有已接入的客户端连接、对应的处理线程,不要只存最后一个启动的线程(原代码里的t字段会不断被新线程覆盖,之前接入的连接完全脱离管控)。
  • 针对性处理AcceptTcpClient()抛出的SocketException:如果是服务主动停止触发的异常,直接正常退出循环即可,不要当成运行错误处理。
  • 停止服务时按顺序执行:先标记停止状态、再关闭监听器阻止新连接接入、再遍历所有活跃客户端挨个Close唤醒阻塞在读写的线程、最后等待所有客户端处理线程退出后再走完停止流程。
  • 所有手动启动的工作线程全部设置IsBackground = true,极端场景下就算停止逻辑没走完,CLR也会在进程退出时自动终止这些线程,不会留残留进程。
  • 删除原来冗余的Task.Run+CancellationToken包装逻辑,把监听逻辑放到TCPService内部的独立后台线程跑,不要阻塞调用方。
修正后的代码

TCPService类

public class TCPService
{
    private const int port = 8045;
    private bool running;
    private TcpListener server = null;
    // 线程安全集合存储所有活跃客户端连接
    private readonly List<TcpClient> _connectedClients = new List<TcpClient>();
    // 存储所有客户端处理线程,停止时等待退出
    private readonly List<Thread> _clientThreads = new List<Thread>();

    public ammendDeviceCountDelegate handleDeviceCount;

    public void StartServer()
    {
        string hostName = Dns.GetHostName();
        IPAddress myIP = GetIPAddress(hostName);
        server = new TcpListener(myIP, port);
        server.Start();
        running = true;
        // 监听逻辑跑在独立后台线程,不阻塞调用方
        Thread listenerThread = new Thread(StartListener);
        listenerThread.IsBackground = true;
        listenerThread.Start();
    }

    public void StopServer()
    {
        running = false;
        // 先停监听器,阻止新连接接入
        try { server?.Stop(); } catch { }
        // 关闭所有已连接客户端,唤醒阻塞在读写的线程
        lock (_connectedClients)
        {
            foreach (var client in _connectedClients)
            {
                try { client?.Close(); } catch { }
            }
            _connectedClients.Clear();
        }
        // 等待客户端线程退出,最多等1秒防止卡退出
        foreach (var t in _clientThreads)
        {
            t.Join(1000);
        }
        _clientThreads.Clear();
    }

    private void StartListener()
    {
        while (running)
        {
            try
            {
                TcpClient client = server.AcceptTcpClient();
                Console.WriteLine("新设备接入");
                // 新客户端加入统一管理集合
                lock (_connectedClients)
                {
                    _connectedClients.Add(client);
                }
                // 启动客户端处理线程
                Thread clientThread = new Thread(HandleDevice);
                clientThread.IsBackground = true;
                lock (_clientThreads)
                {
                    _clientThreads.Add(clientThread);
                }
                clientThread.Start(client);
            }
            catch (SocketException)
            {
                // 主动停止触发的异常直接忽略,退出循环
                break;
            }
        }
    }

    private void HandleDevice(Object obj)
    {
        TcpClient client = (TcpClient)obj;
        handleDeviceCount(false);
        try
        {
            using (client)
            using (var stream = client.GetStream())
            {
                byte[] bytes = new Byte[256];
                int i;
                // 连接断开或被主动关闭时,Read会返回0或抛异常,直接退出循环
                while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    string data = Encoding.ASCII.GetString(bytes, 0, i);
                    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: 收到数据: {data}");

                    string replyStr = "handshaky";
                    Byte[] reply = Encoding.ASCII.GetBytes(replyStr);
                    stream.Write(reply, 0, reply.Length);
                    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: 发送响应: {replyStr}");
                }
            }
        }
        catch (Exception e)
        {
            // 非主动停止场景下的异常才打印日志
            if (running)
            {
                Console.WriteLine($"客户端连接异常: {e.Message}");
            }
        }
        finally
        {
            // 从管理集合移除已断开的客户端
            lock (_connectedClients)
            {
                _connectedClients.Remove(client);
            }
            handleDeviceCount(true);
        }
    }

    private static IPAddress GetIPAddress(string hostname)
    {
        IPHostEntry host = Dns.GetHostEntry(hostname);
        foreach (IPAddress ip in host.AddressList)
        {
            if (ip.AddressFamily == AddressFamily.InterNetwork)
            {
                return ip;
            }
        }
        return IPAddress.None;
    }
}

退出调用逻辑

直接在主窗体的FormClosing事件里调用停止方法即可,不需要原来冗余的Task包装:

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    tcp.StopServer();
}

如果是在Program类里处理退出逻辑,就在Application.Run()返回之后调用tcp.StopServer()就行。

内容的提问来源于stack exchange,提问作者832049823

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:15:26