带有取消令牌的 NetworkStream.ReadAsync 永远不会取消 [英] NetworkStream.ReadAsync with a cancellation token never cancels

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

问题描述

这是证明.
知道这段代码有什么问题吗?

Here the proof.
Any idea what is wrong in this code ?

    [TestMethod]
    public void TestTest()
    {
        var tcp = new TcpClient() { ReceiveTimeout = 5000, SendTimeout = 20000 };
        tcp.Connect(IPAddress.Parse("176.31.100.115"), 25);
        bool ok = Read(tcp.GetStream()).Wait(30000);
        Assert.IsTrue(ok);
    }

    async Task Read(NetworkStream stream)
    {
        using (var cancellationTokenSource = new CancellationTokenSource(5000))
        {
            int receivedCount;
            try
            {
                var buffer = new byte[1000];
                receivedCount = await stream.ReadAsync(buffer, 0, 1000, cancellationTokenSource.Token);
            }
            catch (TimeoutException e)
            {
                receivedCount = -1;
            }
        }
    }

推荐答案

我终于找到了解决方法.使用 Task.WaitAny 将异步调用与延迟任务 (Task.Delay) 结合起来.当延迟在 io 任务之前过去时,关闭流.这将强制任务停止.您应该正确处理 io 任务上的异步异常.并且你应该为延迟任务和io任务添加一个延续任务.

I finally found a workaround. Combine the async call with a delay task (Task.Delay) using Task.WaitAny. When the delay elapses before the io task, close the stream. This will force the task to stop. You should handle the async exception on the io task correctly. And you should add a continuation task for both the delayed task and the io task.

它也适用于 tcp 连接.在另一个线程中关闭连接(您可以认为它是延迟任务线程)会强制所有使用/等待此连接的异步任务停止.

It also work with tcp connections. Closing the connection in another thread (you could consider it is the delay task thread) forces all async tasks using/waiting for this connection to stop.

--编辑--

@vtortola 建议的另一种更简洁的解决方案:使用取消令牌注册对流的调用.关闭:

Another cleaner solution suggested by @vtortola: use the cancellation token to register a call to stream.Close:

async ValueTask Read(NetworkStream stream, TimeSpan timeout = default)
{
    if(timeout == default(TimeSpan))
      timeout = TimeSpan.FromSeconds(5);

    using var cts = new CancellationTokenSource(timeout); //C# 8 syntax
    using(cts.Token.Register(() => stream.Close()))
    {
       int receivedCount;
       try
       {
           var buffer = new byte[30000];
           receivedCount = await stream.ReadAsync(buffer, 0, 30000, tcs.Token).ConfigureAwait(false);
       }
       catch (TimeoutException)
       {
           receivedCount = -1;
       }
    }
}

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

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