C# 使用TcpListener取消NetworkStream.ReadAsync

C# 使用TcpListener取消NetworkStream.ReadAsync,c#,stream,async-await,tcplistener,cancellation,C#,Stream,Async Await,Tcplistener,Cancellation,考虑以下简化示例(准备在LinqPad中滚动,需要提升帐户): 如果我将telnet客户端连接到localhost:6666,然后无所事事地坐了5秒钟,为什么我会看到“令牌已取消”,但从未看到“boom”(或“finished”) 此网络流是否不考虑取消 我可以通过组合使用Task.Delay()和Task.wheny来解决这个问题,但我更愿意让它按预期工作 相反,以下取消示例: async void Go(CancellationToken ct) { using(var cts=ne

考虑以下简化示例(准备在LinqPad中滚动,需要提升帐户):

如果我将telnet客户端连接到
localhost:6666
,然后无所事事地坐了5秒钟,为什么我会看到“令牌已取消”,但从未看到“boom”(或“finished”)

此网络流是否不考虑取消

我可以通过组合使用
Task.Delay()
Task.wheny
来解决这个问题,但我更愿意让它按预期工作

相反,以下取消示例:

async void Go(CancellationToken ct)
{
    using(var cts=new CancellationTokenSource(TimeSpan.FromSeconds(5)))
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(10),cts.Token)
                                        .ConfigureAwait(false);
        }
        catch(TaskCanceledException)
        {
            Console.WriteLine("boom");
        }
    }
}

按预期打印“boom”。发生了什么事?

否,
NetworkStream
不支持取消

不幸的是,底层Win32 API并不总是支持每次操作取消。传统上,您可以取消某个特定句柄的所有I/O,但这是相当新的。大多数.NET BCL都是针对XP API(或更早版本)编写的,其中不包括
CancelIoEx

Stream
通过“伪造”对取消(以及异步I/O)的支持(即使实现不支持)来加剧这个问题。对取消的“假”支持只是立即检查令牌,然后启动无法取消的常规异步读取。这就是您在
NetworkStream
中看到的情况

对于套接字(以及大多数Win32类型),如果要中止通信,传统方法是关闭句柄。这会导致所有当前操作(读取和写入)失败。从技术上讲,这违反了BCL线程安全性,但它确实有效

cts.Token.Register(() => client.Close());
...
catch (ObjectDisposedException)
另一方面,如果您希望检测半开放场景(您的一方正在读取,但另一方已失去连接),那么最好的解决方案是定期发送数据。我会更详细地描述这一点

cts.Token.Register(() => client.Close());
...
catch (ObjectDisposedException)