首页 文章

async / await会影响tcp服务器的性能吗?

提问于
浏览
6

我在C#5.0中创建一个Tcp服务器,我在调用 tcpListener.AcceptTcpClientAsyncnetworkStream.ReadAsync 时使用了await关键字

但是,当我使用Process Explorer检查我的服务器的CPU使用率时,我得到以下结果:

Tcp Sync版本: 10% CPU usage

Tcp异步版本: 30% CPU usage 一半的使用是内核使用 .

此外,我通过在网络流的外观中添加计数器来测量我接收数据的时间,并且异步版本循环120,000次,而同步版本循环2,500,000次 .

在收到的消息/秒中,当从3个不同的客户端接收消息时,异步版本比同步版本慢15% .

Why does the Async Version use a lot more CPU than the Sync version?

这是因为async / await关键字?

Is this normal that an Async Tcp server is slower than its sync counterpart?

EDIT: Here is an example of the async tcp server code

public class AsyncTcpListener : ITcpListener
{ 
    private readonly ServerEndpoint _serverEndPoint;  // Custom class to store IpAddress and Port

    public bool IsRunning { get; private set; }

    private readonly List<AsyncTcpClientConnection> _tcpClientConnections = new List<AsyncTcpClientConnection>(); 

    private TcpListener _tcpListener;

    public AsyncTcpMetricListener()
    {
        _serverEndPoint = GetServerEndpoint();  
    }

    public async void Start()
    {
        IsRunning = true;

        RunTcpListener();
    }

    private void MessageArrived(byte[] buffer)
    { 
        // Deserialize
    }

    private void RunTcpListener(){
       _tcpListener = null;
        try
        {
            _tcpListener = new TcpListener(_serverEndPoint.IpAddress, _serverEndPoint.Port);
            _tcpListener.Start();
            while (true)
            {
                var tcpClient = await _tcpListener.AcceptTcpClientAsync().ConfigureAwait(false);
                var asyncTcpClientConnection = new AsyncTcpClientConnection(tcpClient,  MessageArrived);
                _tcpClientConnections.Add(asyncTcpClientConnection);
            }
        } 
        finally
        {
            if (_tcpListener != null)
                _tcpListener.Stop();

            IsRunning = false;
        }
    }

    public void Stop()
    {
        IsRunning = false; 
        _tcpListener.Stop();
        _tcpClientConnections.ForEach(c => c.Close());
    }
}

对于每个新客户端,我们创建一个新的AsyncTcpConnection

public class AsyncTcpClientConnection
{ 
    private readonly Action<byte[]> _messageArrived;
    private readonly TcpClient _tcpClient; 

    public AsyncTcpClientConnection(TcpClient tcpClient, Action<byte[]> messageArrived)
    {
        _messageArrived = messageArrived;
        _tcpClient = tcpClient; 
        ReceiveDataFromClientAsync(_tcpClient); 
    }

    private async void ReceiveDataFromClientAsync(TcpClient tcpClient)
    {
        var readBuffer = new byte[2048];
        // PacketProtocol class comes from http://blog.stephencleary.com/2009/04/sample-code-length-prefix-message.html
        var packetProtocol = new PacketProtocol(2048);  
        packetProtocol.MessageArrived += _messageArrived;

        try
        {
            using (tcpClient)
            using (var networkStream = tcpClient.GetStream())
            {
                int readSize;
                while ((readSize = await networkStream.ReadAsync(readBuffer, 0, readBuffer.Length).ConfigureAwait(false)) != 0)
                {
                    packetProtocol.DataReceived(readBuffer, readSize); 
                }
            }
        } 
        catch (Exception ex)
        {
            // log
        } 
    } 

    public void Close()
    {
        _tcpClient.Close();
    }
}

EDIT2: Synchronous server

public class TcpListener : ITcpListener
{  
    private readonly ObserverEndpoint _serverEndPoint; 
    private readonly List<TcpClientConnection> _tcpClientConnections = new List<TcpClientConnection>();

    private Thread _listeningThread;
    private TcpListener _tcpListener;
    public bool IsRunning { get; private set; }

    public TcpMetricListener()
    {
        _serverEndPoint = GetServerEndpoint();   

    }


    public void Start()
    {
        IsRunning = true;
        _listeningThread = BackgroundThread.Start(RunTcpListener);  
    }

    public void Stop()
    {
        IsRunning = false;

        _tcpListener.Stop();
        _listeningThread.Join();
        _tcpClientConnections.ForEach(c => c.Close());
    }

    private void MessageArrived(byte[] buffer)
    {
        // Deserialize
    }

    private void RunTcpListener()
    {
        _tcpListener = null;
        try
        {
            _tcpListener = new TcpListener(_serverEndPoint.IpAddress, _serverEndPoint.Port);
            _tcpListener.Start();
            while (true)
            {
                var tcpClient = _tcpListener.AcceptTcpClient();
                _tcpClientConnections.Add(new TcpClientConnection(tcpClient, MessageArrived));
            }
        } 
        finally
        {
            if (_tcpListener != null)
                _tcpListener.Stop();

            IsRunning = false;
        }
    }
}

和连接

public class TcpClientConnection
{ 
    private readonly Action<byte[]> _messageArrived;
    private readonly TcpClient _tcpClient;
    private readonly Task _task; 
    public TcpClientConnection(TcpClient tcpClient,   Action<byte[]> messageArrived)
    {
        _messageArrived = messageArrived;
        _tcpClient = tcpClient; 
        _task = Task.Factory.StartNew(() => ReceiveDataFromClient(_tcpClient), TaskCreationOptions.LongRunning);

    }

    private void ReceiveDataFromClient(TcpClient tcpClient)
    {
        var readBuffer = new byte[2048];
        var packetProtocol = new PacketProtocol(2048);
        packetProtocol.MessageArrived += _messageArrived;


            using (tcpClient)
            using (var networkStream = tcpClient.GetStream())
            {
                int readSize;
                while ((readSize = networkStream.Read(readBuffer, 0, readBuffer.Length)) != 0)
                {
                    packetProtocol.DataReceived(readBuffer, readSize); 
                }
            } 
    }


    public void Close()
    {
        _tcpClient.Close();
        _task.Wait();
    }
}

2 回答

  • 0

    我也有 async 的问题,这些是我的发现:https://stackoverflow.com/a/22222578/307976

    此外,我有一个异步TCP服务器/客户端使用 async 示例here可以很好地扩展 .

  • 0

    尝试使用 ReceiveInt32AsyncReceiveDataAsync 的以下实现来直接接收长度为前缀的消息,而不是使用 tcpClient.GetStreamnetworkStream.ReadAsync

    public static class SocketsExt
    {
        static public async Task<Int32> ReceiveInt32Async(
            this TcpClient tcpClient)
        {
            var data = new byte[sizeof(Int32)];
            await tcpClient.ReceiveDataAsync(data).ConfigureAwait(false);
            return BitConverter.ToInt32(data, 0);
        }
    
        static public Task ReceiveDataAsync(
            this TcpClient tcpClient,
            byte[] buffer)
        {
            return Task.Factory.FromAsync(
                (asyncCallback, state) =>
                    tcpClient.Client.BeginReceive(buffer, 0, buffer.Length, 
                        SocketFlags.None, asyncCallback, state),
                (asyncResult) =>
                    tcpClient.Client.EndReceive(asyncResult), 
                null);
        }
    }
    

    看看这是否有任何改进 . 另外,我还建议将 ReceiveDataFromClientAsync 设为 async Task 方法并将 Task 存储在 AsyncTcpClientConnection 内(用于状态和错误跟踪) .

相关问题