10

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

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

更新:
尝试应用此处描述的方法取消异步操作

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

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

4

4 回答 4

12

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

对我来说,我解决了这样的问题,我做了一个扩展来添加一个需要额外超时的方法 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 意味着读取超时

于 2016-12-16T23:16:40.740 回答
6

编辑:以下要点是我为此所做的解决方法。 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 调用似乎会泄漏,在我的情况下,它似乎读取了稍后的写入,并且数据基本上返回到内存中不再使用的位置。

于 2017-07-31T21:52:34.713 回答
3

传递 a CancellationTokentoReadAsync()不会增加太多价值,因为它仅在 开始读取之前检查令牌的状态:

// 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);

您可以很容易地自己添加一个 timout 机制,方法是等待ReadAsync()或 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}");
}
于 2020-03-16T12:55:52.883 回答
-1

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

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

而不是使用例如

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

只需使用

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

这尊重 stream.ReadTimeout 就好了。

于 2021-10-02T19:48:22.187 回答