首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >带有取消令牌的NetworkStream.ReadAsync从不取消

带有取消令牌的NetworkStream.ReadAsync从不取消
EN

Stack Overflow用户
提问于 2012-09-14 17:45:13
回答 4查看 24.8K关注 0票数 42

这就是证据。

你知道这段代码出了什么问题吗?

代码语言:javascript
运行
复制
    [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;
            }
        }
    }
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-10-15 17:43:36

我终于找到了一个变通办法。使用Task.WaitAny将异步呼叫与延迟任务(Task.Delay)相结合。当io任务之前经过延迟时,关闭流。这将强制任务停止。您应该正确处理io任务上的异步异常。并且您应该为延迟任务和io任务添加一个延续任务。

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

-编辑--

@vtortola建议的另一个更干净的解决方案是:使用取消令牌注册对stream.Close的调用:

代码语言:javascript
运行
复制
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;
       }
    }
}
票数 35
EN

Stack Overflow用户

发布于 2012-09-15 04:35:56

取消是协作的。NetworkStream.ReadAsync必须配合才能被取消。它很难做到这一点,因为这可能会使流处于未定义的状态。哪些字节已从Windows TCP堆栈读取,哪些尚未读取?IO是不容易取消的。

Reflector显示NetworkStream不会覆盖ReadAsync。这意味着它将获得Stream.ReadAsync的默认行为,即只丢弃令牌。没有通用的方法可以取消流操作,因此BCL Stream类甚至不尝试(它不能尝试-没有办法做到这一点)。

您应该在Socket上设置超时。

票数 22
EN

Stack Overflow用户

发布于 2020-04-09 09:50:00

提供了关于三种不同方法的更多上下文。我的服务监视其他web应用程序的可用性。因此,它需要建立到各种网站的大量连接。它们中的一些崩溃/返回错误/变得无响应。

轴Y-挂起的测试(会话)数。由于部署/重新启动而降至0。

I. (1月25日)在修改服务后,最初的实现使用了带有取消令牌的ReadAsync。这导致了许多测试挂起(对这些网站运行请求表明服务器有时确实不返回内容)。

II. (2月17日)部署了一个更改,以保护与Task.Delay的取消。这完全解决了这个问题。

代码语言:javascript
运行
复制
private async Task<int> StreamReadWithCancellationTokenAsync(Stream stream, byte[] buffer, int count, Task cancellationDelayTask)
{
    if (cancellationDelayTask.IsCanceled)
    {
        throw new TaskCanceledException();
    }

    // Stream.ReadAsync doesn't honor cancellation token. It only checks it at the beginning. The actual
    // operation is not guarded. As a result if remote server never responds and connection never closed
    // it will lead to this operation hanging forever.
    Task<int> readBytesTask = stream.ReadAsync(
        buffer,
        0,
        count);
    await Task.WhenAny(readBytesTask, cancellationDelayTask).ConfigureAwait(false);

    // Check whether cancellation task is cancelled (or completed).
    if (cancellationDelayTask.IsCanceled || cancellationDelayTask.IsCompleted)
    {
        throw new TaskCanceledException();
    }

    // Means that main task completed. We use Result directly.
    // If the main task failed the following line will throw an exception and
    // we'll catch it above.
    int readBytes = readBytesTask.Result;

    return readBytes;
}

III (3月3日)之后,StackOverflow实现了基于超时的流关闭:

代码语言:javascript
运行
复制
using (timeoutToken.Register(() => stream.Close()))
{
    // Stream.ReadAsync doesn't honor cancellation token. It only checks it at the beginning. The actual
    // operation is not guarded. As a result if a remote server never responds and connection never closed
    // it will lead to this operation hanging forever.
    // ReSharper disable once MethodSupportsCancellation
    readBytes = await targetStream.ReadAsync(
        buffer,
        0,
        Math.Min(responseBodyLimitInBytes - totalReadBytes, buffer.Length)).ConfigureAwait(false);
}

这种实现带来了挂起(与最初的方法不同):

已恢复到Task.Delay解决方案。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12421989

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档