Cancel C# 4.5 TcpClient ReadAsync by timeout

前端 未结 4 463
北海茫月
北海茫月 2021-01-11 23:32

What would the proper way to cancel TcpClient ReadAsync operation by timeout and catch this timeout event in .NET 4.5?

TcpClient.ReadTimeout seems to be applied to

相关标签:
4条回答
  • 2021-01-11 23:51

    Passing a CancellationToken to ReadAsync() doesn't add much value because it checks the status of the token only before 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);
    

    You can quite easily add a timout mechanism yourself by waiting for either the ReadAsync() or the timout task to complete like this:

    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}");
    }
    
    0 讨论(0)
  • 2021-01-11 23:58

    You have to implement it by yourself: it is not available in that class.

    you should use a timer that will kill the async operation after a timespan or use the Read sync version in a a backgroundthread.

    there you can find an example: Implementing a timeout with NetworkStream.BeginRead and NetworkStream.EndRead

    0 讨论(0)
  • 2021-01-12 00:02

    so I know that was from a long time but google still drive me here and I saw there is none marked as answer

    for me, I solve like that I make an extension to add a method ReadAsync that takes extra timeout

    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;
    }
    

    so if it returned -1 that mean the read timed out

    0 讨论(0)
  • 2021-01-12 00:15

    Edit: the following gist is a workaround I did for this. https://gist.github.com/svet93/fb96d8fd12bfc9f9f3a8f0267dfbaf68

    Original:

    I really liked the answer from Khalid Omar and did my own version of it which I thought was worth sharing:

    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;
        }
    }
    

    It is more or less the same thing except slightly differently formatted in a way that is more readable (to me), and more importantly it does not use Task.Run I wasn't sure why it was used in his example.

    Edit:

    The above may seem good in a first glance but I found out it causes some problems. The readAsync call seems to leak if no data comes in, and in my case it seems that it reads a later write and the data is essentially returned within a place in memory no longer used.

    0 讨论(0)
提交回复
热议问题