C#监听器线程的高CPU使用率,睡眠未命中断开

时间:2017-04-10 15:42:20

标签: c# multithreading winforms tcplistener tcpserver

我的连接处理程序如下(这是个人实验而不是生产代码)

如果我没有在while循环中的任何地方添加Thread.Sleep,它会开始吸收CPU。相反,如果我做睡眠以缓解无休止的垃圾邮件,我会错过断开连接.. CPU上升与运行的客户端/线程数量成正比,因此不是导致高使用率的监听器本身,而是下面发布的实际客户端线程。任何人对如何解决这个问题都有任何想法?

(我正在避免基于等待的解决方案,因为我对async / await不够熟悉,并且线程方法对于这个相当小的项目工作正常)

我只是在SO周围寻找解决方案,并没有注意到任何特定的问题或提供解决方案而不是指导人们异步/等待文章,所以很抱歉,如果我错过了适用的答案。< / p>

        private void HandleConnection(CancellationToken ct) {
        int recv = 0;
        byte[] buf = new byte[4096];
        Trace.WriteLine($"{_name} Connected");
        if (_ns.CanWrite && _client.Connected) {
            _ns.Write(Encoding.BigEndianUnicode.GetBytes("■WEL"), 0, Encoding.BigEndianUnicode.GetBytes("■WEL").Length);
            try {
                while (_client.Connected && !ct.IsCancellationRequested) {

                    while (!_ns.DataAvailable) { //first attempted solution
                        Thread.Sleep(100); // miss discon if i sleep here
                        }

                    if (ct.IsCancellationRequested) {
                        Trace.WriteLine($"{(string)this} thread aborting");
                        break;
                        }

                    buf = new byte[4096];

                    if (_client.Connected && _ns.DataAvailable) {

                        recv = _ns.Read(buf, 0, buf.Length);
                        } else {
                        recv = 0;
                        }

                    if (recv > 0) {

                        string r = Encoding.BigEndianUnicode.GetString(buf);
                        r = r.TrimEnd('\0');
                        if (String.IsNullOrEmpty(r) || String.IsNullOrWhiteSpace(r))
                            r = null; //need the !not version
                        else
                            if (ParseMsg(r))
                                break;
                        }

                    //Thread.Sleep(100); // also miss discon here too

                    }
                } catch (IOException ioe) { }
            Trace.WriteLine($"{_name} Disconnected");
            if (OnDisconnected != null)
                OnDisconnected(this);
            }
        }

2 个答案:

答案 0 :(得分:3)

通过套接字进行通信的正确方法是:

  1. 继续阅读。这些读操作将一直阻塞,直到数据进入或直到套接字正常断开(读取完成时读取0字节为止)。
  2. 定期写。 These writes are required to ensure the connection is still viable
  3. 正确的线程方法需要每个连接两个线程。我不相信它比异步方法更简单。

    P.S。如果您的代码使用Connected,则会出现错误。正确的解决方案永远不需要使用Connected

答案 1 :(得分:1)

我遇到了与你相同的问题,但我发现解决这个问题的最佳方法是:

不使用睡眠和线程阻止套接字。

升级如果您使用线程并进入服务器,则每个连接接收和回复每条消息的性能都会很低。

如果您需要高性能应用程序,则不得使用sleeps或为您接受的每个连接创建线程。最好的方法是使用NetworkStream提供的Asyncronous方法,使用BeginReadEndRead,例如:

    public void run()
    {
        server = new TcpListener(IPAddress.Any, port);
        server.Start();

        log.Info("Starting SocketServer on Port [" + port + "]");

        while (keepRunning)
        {
            try
            {
                TcpClient socket = server.AcceptTcpClient();
                if (keepRunning)
                    RequestManager.createRequestForEvalue(socket, idLayout);
            }
            catch (Exception ex)
            {
                log.Error(ex.Message);
                log.Error(ex.StackTrace);
            }
        }

        log.Info("Server Stoped.");
    }

    public static bool createRequestForEvalue(TcpClient socket, int idLayout)
    {
        Request req = null;
        req = new Request(socket,idLayout);

        registerRequest(req.ID,req); //Registra el Request, para su posterior uso.

        // DO NOT CREATE THREADS FOR ATTEND A NEW CONNECTION!!!
        //Task.Factory.StartNew(req.RunForIVR);
        //ThreadPool.QueueUserWorkItem(req.RunForIVR);

        req.startReceiveAsync(); //Recive data in asyncronus way.
        return true;
    }

    public void startReceiveAsync()
    {
        try
        {
            log.Info("[" + id + "] Starting to read the Request.");
            requestBuffer = new byte[BUFFER_SIZE];
            NetworkStream nst = socket.GetStream();
            nst.BeginRead(requestBuffer, 0,BUFFER_SIZE, this.requestReceived, nst);
        }catch(Exception ex)
        {
            log.Error("[" + id + "] There was a problem to read the Request: " + ex.Message);
            RequestManager.removeRequest(id);
            closeSocket();
        }
    }

    public void requestReceived(IAsyncResult ar)
    {

        try
        {   
        NetworkStream nst = socket.GetStream();
        int bread = nst.EndRead(ar); //Block the socket until all the buffer has been available.
        message = Encoding.UTF8.GetString(requestBuffer, 0, BUFFER_SIZE);
            log.Info("[" + id + "] Request recived: [" + message +"]");
            RunForIVR();
        }
        catch (Exception ex)
        {
            log.Error("[" + id + "] There was a problem to read the Request: " + ex.Message);
            RequestManager.removeRequest(id);
            closeSocket();
        }

    }

    public void SendResponse(String Response)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(Response);
        sb.Append('\0', BUFFER_SIZE - Response.Length);
        string message = sb.ToString();

        log.Info("[" + id + "] ivrTrans CMD: [" + idCMD + "] RESPONSE: [" + Response + "]");

        NetworkStream nst = socket.GetStream();
        byte[] buffer = new byte[BUFFER_SIZE];
        for (int i = 0; i < BUFFER_SIZE; i++)
            buffer[i] = (byte)message.ElementAt(i);

        nst.BeginWrite(buffer, 0, BUFFER_SIZE, this.closeSocket, nst);
    }

    public void closeSocket(IAsyncResult ar = null)
    {

        try
        {
            if (ar != null) //Since 4.24
            {
                NetworkStream nst = socket.GetStream();
                nst.EndWrite(ar);
            }

            socket.Close();
            socket = null;
        }catch(Exception ex)
        {
            log.Warn("[" + id + "] There was a problem to close the socket. Error: " + ex.Message + Environment.NewLine + ex.StackTrace);
        }
        log.Info("[" + id + "] Socket closed.");
    }
  

升级我使用EndRead确保请求已全部到达。

通过其他方式,您可以使用BeginWriteEndWrite来了解套接字何时完成写入以关闭连接

通过这种方式,您将以尽可能快的方式参加连接。在我的情况下,我将CPU使用率从30%降低到0%,每小时15K请求。