使用async/await的异步TcpListener的正确方法

vto*_*ola 19 c# asynchronous tcplistener threadpool async-await

我一直在考虑使用异步编程设置TCP服务器的正确方法是什么.

通常我会为每个传入的请求生成一个线程,但我想做大部分的ThreadPool,所以当连接空闲时,没有被阻塞的线程.

首先,我将创建监听器并开始接受客户端,在本例中,在控制台应用程序中:

static void Main(string[] args)
{
    CancellationTokenSource cancellation = new CancellationTokenSource();
    var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8001);
    TcpListener server = new TcpListener(endpoint); 

    server.Start();
    var task = AcceptTcpClients(server, cancellation.Token);

    Console.ReadKey(true);
    cancellation.Cancel();
    await task;
    Console.ReadKey(true);
}
Run Code Online (Sandbox Code Playgroud)

在该方法中,我将循环接受传入的请求并生成一个新的Task来处理连接,因此循环可以返回以接受更多的客户端:

static async Task AcceptTcpClients(TcpListener server, CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        var ws = await server.AcceptTcpClientAsync();

        Task.Factory.StartNew(async () =>
        {
            while (ws.IsConnected && !token.IsCancellationRequested)
            {
                String msg = await ws.ReadAsync();
                if (msg != null)
                    await ws.WriteAsync(ProcessResponse(msg));
            }
        }, token);
    }
 }
Run Code Online (Sandbox Code Playgroud)

创建新任务并不一定意味着新线程,但这是正确的方法吗?我是利用ThreadPool还是我还能做什么?

这种方法有潜在的缺陷吗?

Ste*_*ary 17

在await task;你Main将无法编译; task.Wait();如果你想阻止,你将不得不使用它.

此外,您应该使用Task.Run而不是Task.Factory.StartNew在异步编程中.

创建新任务并不一定意味着新线程,但这是正确的方法吗?

你当然可以启动单独的任务(使用Task.Run).虽然你不会有来.您可以轻松地调用单独的async方法来处理各个套接字连接.

但是,实际的套接字处理存在一些问题.该Connected物业几乎没用.即使在写入时,也应始终连续读取已连接的套接字.此外,您应该编写"keepalive"消息或读取超时,以便您可以检测半开放情况.我维护一个TCP/IP .NET常见问题解答,解释这些常见问题.

我真的强烈建议人们不要不写TCP/IP服务器或客户端.有吨的陷阱.如果可能的话,自我托管WebAPI和/或SignalR会好得多.


asc*_*eck 5

为了优雅地停止服务器接受循环,我注册了一个回调,该回调在取消取消令牌时停止侦听 ( cancellationToken.Register(listener.Stop);)。

这将抛出一个await listener.AcceptTcpClientAsync();很容易捕获的 SocketException 。

不需要 Task.Run(HandleClient()),因为调用异步方法会返回一个并行运行的任务。

    public async Task Run(CancellationToken cancellationToken)
    {
        TcpListener listener = new TcpListener(address, port);
        listener.Start();
        cancellationToken.Register(listener.Stop);
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                TcpClient client = await listener.AcceptTcpClientAsync();
                var clientTask = protocol.HandleClient(client, cancellationToken)
                    .ContinueWith(antecedent => client.Dispose())
                    .ContinueWith(antecedent => logger.LogInformation("Client disposed."));
            }
            catch (SocketException) when (cancellationToken.IsCancellationRequested)
            {
                logger.LogInformation("TcpListener stopped listening because cancellation was requested.");
            }
            catch (Exception ex)
            {
                logger.LogError(new EventId(), ex, $"Error handling client: {ex.Message}");
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)