TcpListener在侦听来自TcpClients的传入消息时阻止线程

问题描述

我创建了一个新的.NET Core工作者服务项目。此服务还应充当TCP套接字服务器,并侦听传入的消息。因此,基于the code sample from the docs,这基本上就是我要做的事情

public class Worker : BackgroundService
{
    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"),1234);
        tcpListener.Start();
        
        try
        {
            while (true)
            {
                TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync();
                NetworkStream tcpClientStream = tcpClient.GetStream();
                using StreamReader streamReader = new StreamReader(tcpClientStream);
                string messageText = await streamReader.ReadToEndAsync();
        
                // ... do things with messageText ...
            }
        }
        catch (Exception exception)
        {
            // ... error handling ...
        }

        await base.StartAsync(cancellationToken);
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await Task.Delay(1000,stoppingToken);
        }
    }
}

将不再调用ExecuteAsync方法,因为代码被卡在while方法StartAsync循环中。当我注释掉整个重写的StartAsync方法时,一切正常,ExecuteAsync方法调用

是否有可能摆脱while循环的阻塞并利用事件处理程序或类似的东西?

解决方法

一个BackgroundService的典型模式是它简单地覆盖ExecuteAsync并等待stoppingToken发出信号然后立即退出。

看看BackgroundService的源代码。

如果要使用“开始/停止”模式,请实施IHostedService

如果要使用BackgroundService,则应这样布置:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"),1234);
    tcpListener.Start();
    
    try
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync(); // READ BELOW ABOUT THIS
            NetworkStream tcpClientStream = tcpClient.GetStream();
            using StreamReader streamReader = new StreamReader(tcpClientStream);
            string messageText = await streamReader.ReadToEndAsync();
    
            // ... do things with messageText ...
        }
    }
    catch (Exception exception)
    {
        // ... error handling ...
    }
}

现在,这里最大的问题是AccpetTcpClientAsync没有取消功能。因此,您可能必须实现something like this

所以您可以这样做:

var tcpClient = await tcpListener.AcceptTcpClientAsync().WithCancellation(stoppingToken);

然后吞下在这种情况下将抛出的OperationCancelledExeption

编辑后添加

另一个选择是您可以一起跳过异步/等待版本,并使用Begin../End...(在这种情况下为BeginAcceptTcpClient())。我倾向于沿着这条路走很多路。连接某些东西时触发事件触发是非常方便的。请记住,此时您的TcpListener必须在类范围内,以免被丢弃。