简体   繁体   English

使用TcpListener取消NetworkStream.ReadAsync

[英]Cancel NetworkStream.ReadAsync using TcpListener

Consider the following simplified example (ready to roll in LinqPad, elevated account required): 考虑下面的简化示例(准备在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();
    }
}

If I connect a telnet client to localhost:6666 and sit around doing nothing for 5 seconds, why do I see "Token was canceled" but never see "boom" (or "finished")? 如果我将telnet客户端连接到localhost:6666并无所事事5秒钟,为什么我看到“令牌已取消”却却看不到“繁荣”(或“完成”)?

Will this NetworkStream not respect cancellation? 此NetworkStream是否不尊重取消?

I can work around this with a combination of Task.Delay() and Task.WhenAny , but I'd prefer to get it working as expected. 我可以结合使用Task.Delay()Task.WhenAny来解决此Task.WhenAny ,但我希望它能按预期工作。

Conversely, the following example of cancellation: 相反,以下取消示例:

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");
        }
    }
}

Prints "boom", as expected. 按预期打印“景气”。 What's going on? 这是怎么回事?

No, NetworkStream does not support cancellation. 否, NetworkStream不支持取消。

Unfortunately, the underlying Win32 APIs do not always support per-operation cancellation. 不幸的是,底层的Win32 API并不总是支持按操作取消。 Traditionally, you could cancel all I/O for a particular handle, but the method to cancel a single I/O operation is fairly recent. 传统上,您可以取消特定句柄的所有 I / O,但是取消单个 I / O操作的方法是相当新的。 Most of the .NET BCL was written against the XP API (or older), which did not include CancelIoEx . 大部分.NET BCL是针对XP API(或更早版本)编写的,其中不包括CancelIoEx

Stream compounds this issue by "faking" support for cancellation (and asynchronous I/O, too) even if the implementation doesn't support it. Stream通过“伪造”对取消的支持(以及异步I / O)来加重这个问题,即使实现不支持它。 The "fake" support for cancellation just checks the token immediately and then starts a regular asynchronous read that cannot be cancelled. 对取消的“伪造”支持只是立即检查令牌,然后启动无法取消的常规异步读取。 That's what you're seeing happen with NetworkStream . 那就是您所看到的NetworkStream发生的情况。

With sockets (and most Win32 types), the traditional approach is to close the handle if you want to abort communications. 对于套接字(和大多数Win32类型),传统的方法是在要中止通信时关闭句柄。 This causes all current operations (both reads and writes) to fail. 这将导致所有当前操作(读取和写入)均失败。 Technically this is a violation of BCL thread safety as documented, but it does work. 从技术上讲,这违反了记录的BCL线程安全性,但是确实可以。

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

If, on the other hand, you want to detect a half-open scenario (where your side is reading but the other side has lost its connection), then the best solution is to periodically send data. 另一方面,如果您要检测一个半开的情况(您的一侧正在读取但另一侧已失去连接),那么最好的解决方案是定期发送数据。 I describe this more on my blog . 在博客上对此进行了更多描述。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM