c# - 使用 TcpListener 取消 NetworkStream.ReadAsync

标签 c# stream async-await tcplistener cancellation

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

void Main()
{
    Go();
    Thread.Sleep(100000);
}
async void Go()
{
    TcpListener listener = new TcpListener(IPAddress.Any, 6666);
    try
    {
        cts.Token.Register(() => Console.WriteLine("Token was canceled"));
        listener.Start();
        using(TcpClient client = await listener.AcceptTcpClientAsync()
                                               .ConfigureAwait(false))
        using(var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
        {
            var stream=client.GetStream();
            var buffer=new byte[64];
            try
            {
                var amtRead = await stream.ReadAsync(buffer,
                                                     0,
                                                     buffer.Length,
                                                     cts.Token);
                Console.WriteLine("finished");
            }
            catch(TaskCanceledException)
            {
                Console.WriteLine("boom");
            }
        }
    }
    finally
    {
        listener.Stop();
    }
}

如果我将一个 telnet 客户端连接到 localhost:6666 并闲坐 5 秒钟什么都不做,为什么我会看到“Token was cancelled”但从未看到“boom”(或“finished”) ?

这个 NetworkStream 会不尊重取消吗?

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

反之,取消的例子如下:

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,但是the method to cancel a single I/O operation是最近的。大多数 .NET BCL 是针对 XP API(或更早版本)编写的,其中不包括 CancelIoEx

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

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

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

另一方面,如果您想检测半开情况(您的一侧正在读取但另一侧已失去连接),那么最好的解决方案是定期发送数据。我对此进行了更多描述 on my blog .

关于c# - 使用 TcpListener 取消 NetworkStream.ReadAsync,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20131434/

相关文章:

c# - .Net 2.0、VS2010 和 Windows 8 上的 "Could not load file or assembly System.Drawing or one of its dependencies"错误

c# - SpicIE 在开发 IE 插件时有哪些限制?

c# - 基类包含字段 'Head1' ,但其类型不兼容

c# - 编译器是否在异步方法链上执行 "return value optimization"

c# - 如何创建可以处理 URL :callto and URL:tel protocols in Windows 10? 的 WPF 应用程序

python - 如何通过 tcp 接收带有线路流的 syslog-ng 日志?

python - 解析Python中分块的内容类型的响应

java - 重新分配输入/输出流?

c# - Task.WaitAll 在 ASP.NET 中挂起多个等待任务

node.js - 如何让我的异步函数返回值而不是 promise ?