【问题标题】:Cancel C# 4.5 TcpClient ReadAsync by timeout通过超时取消 C# 4.5 TcpClient ReadAsync
【发布时间】:2013-02-22 19:18:26
【问题描述】:

通过超时取消 TcpClient ReadAsync 操作并在 .NET 4.5 中捕获此超时事件的正确方法是什么?

TcpClient.ReadTimeout 似乎应用于同步只读。

更新:
尝试应用这里描述的方法Cancelling an Asynchronous Operation

var buffer = new byte[4096];
CancellationTokenSource cts = new CancellationTokenSource(5000);
int amountRead = await tcpClientStream.ReadAsync(buffer, 0, 4096, cts.Token);

但它永远不会因超时而取消。有什么事吗?

【问题讨论】:

  • 我在TcpClient 上没有看到ReadAsync
  • 对不起,这是 NetworkStream.ReadAsync

标签: c# timeout tcpclient


【解决方案1】:

编辑:好的,这锁定了一个线程。但对于许多应用程序,我认为这仍然是一个简单的解决方案。

好的,所以这是一个旧线程,但我想我会分享我自己的解决方案,因为它看起来更简单。

而不是使用例如

int length = await stream.Read(buffer, 0, 2, cancellationToken);

简单地使用

int length = 0;
await Task.Run(() => { length = stream.Read(buffer, 0, 2); });

这尊重 stream.ReadTimeout 就好了。

【讨论】:

  • 运行同步代码会让你的线程休眠
【解决方案2】:

CancellationToken 传递给ReadAsync() 不会增加太多价值,因为它仅在 it starts reading 之前检查令牌的状态:

// If cancellation was requested, bail early with an already completed task.
// Otherwise, return a task that represents the Begin/End methods.
return cancellationToken.IsCancellationRequested
            ? Task.FromCanceled<int>(cancellationToken)
            : BeginEndReadAsync(buffer, offset, count);

您可以通过等待ReadAsync() 或 timout 任务完成来非常轻松地自己添加一个 timout 机制,如下所示:

byte[] buffer = new byte[128];
TimeSpan timeout = TimeSpan.FromSeconds(30);

var readTask = stream.ReadAsync(buffer, 0, buffer.Length);
var timeoutTask = Task.Delay(timeout);
await Task.WhenAny(readTask, timeoutTask);
if (!readTask.IsCompleted)
{
    throw new TimeoutException($"Connection timed out after {timeout}");
}

【讨论】:

    【解决方案3】:

    所以我知道那是很久以前的事了 但谷歌仍然开车送我到这里 我看到没有标记为答案

    对我来说,我就是这样解决的 我做了一个扩展来添加一个需要额外超时的方法 ReadAsync

    public static async Task<int> ReadAsync(this NetworkStream stream, byte[] buffer, int offset, int count, int TimeOut)
    {
        var ReciveCount = 0;
        var receiveTask = Task.Run(async () => { ReciveCount = await stream.ReadAsync(buffer, offset, count); });
        var isReceived = await Task.WhenAny(receiveTask, Task.Delay(TimeOut)) == receiveTask;
        if (!isReceived) return -1;
        return ReciveCount;
    }
    

    所以如果它返回 -1 则意味着读取超时

    【讨论】:

    • 读取是否可能完成导致来自套接字的数据不一致?
    • 我认为不能保证 ReciveCount 的值会被更新,因为它既不是易失的也不是原子的。还是任务完成和任务等待操作会造成内存屏障?
    • 正如这个线程所建议的stackoverflow.com/q/41928139/716769,它可能真的发生在调用者线程中变量不会被更改
    • 怎么称呼?
    • 怎么称呼?
    【解决方案4】:

    编辑:以下要点是我为此所做的解决方法。 https://gist.github.com/svet93/fb96d8fd12bfc9f9f3a8f0267dfbaf68

    原文:

    我真的很喜欢 Khalid Omar 的回答,并做了我自己认为值得分享的版本:

    public static class TcpStreamExtension
    {
        public static async Task<int> ReadAsyncWithTimeout(this NetworkStream stream, byte[] buffer, int offset, int count)
        {
            if (stream.CanRead)
            {
    
                Task<int> readTask = stream.ReadAsync(buffer, offset, count);
                Task delayTask = Task.Delay(stream.ReadTimeout);
                Task task = await Task.WhenAny(readTask, delayTask);
    
                if (task == readTask)
                        return await readTask;
    
            }
            return 0;
        }
    }
    

    它或多或少是相同的东西,只是格式略有不同(对我来说)更易读,更重要的是它不使用Task.Run 我不确定为什么在他的示例中使用它。

    编辑:

    上面的内容乍一看似乎不错,但我发现它会导致一些问题。如果没有数据进入, readAsync 调用似乎会泄漏,在我的情况下,它似乎读取了稍后的写入,并且数据基本上返回到内存中不再使用的位置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-04
      • 2019-09-06
      • 1970-01-01
      • 2016-03-19
      • 1970-01-01
      • 2013-04-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多