使用TcpListener取消NetworkStream.ReadAsync [英] Cancel NetworkStream.ReadAsync using TcpListener

查看:145
本文介绍了使用TcpListener取消NetworkStream.ReadAsync的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下简化示例(准备在LinqPad中使用,需要提升的帐户):

Consider the following simplified example (ready to roll in LinqPad, elevated account required):

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秒钟,为什么我看到令牌已取消"却却看不到繁荣"(或完成")?

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")?

此NetworkStream是否不尊重取消?

Will this NetworkStream not respect cancellation?

我可以结合使用Task.Delay()Task.WhenAny来解决此问题,但我希望它能按预期工作.

I can work around this with a combination of Task.Delay() and Task.WhenAny, but I'd prefer to get it working as expected.

相反,以下取消示例:

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?

推荐答案

否,NetworkStream不支持取消.

不幸的是,底层的Win32 API并不总是支持按操作取消.传统上,您可以为特定的句柄取消 all I/O,但是

Unfortunately, the underlying Win32 APIs do not always support per-operation cancellation. Traditionally, you could cancel all I/O for a particular handle, but the method to cancel a single I/O operation is fairly recent. Most of the .NET BCL was written against the XP API (or older), which did not include CancelIoEx.

Stream通过伪造"对取消的支持(以及异步I/O)来解决此问题,即使实现不支持它也是如此.对取消的伪造"支持只是立即检查令牌,然后启动无法取消的常规异步读取.这就是NetworkStream发生的情况.

Stream compounds this issue by "faking" support for cancellation (and asynchronous I/O, too) even if the implementation doesn't support it. 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.

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

With sockets (and most Win32 types), the traditional approach is to close the handle if you want to abort communications. 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.

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.

这篇关于使用TcpListener取消NetworkStream.ReadAsync的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆