Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cancel C# 4.5 TcpClient ReadAsync by timeout

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 the sync Read only.

UPDATE:
Tried tro apply the approach desribed here Cancelling an Asynchronous Operation

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

but it never cancels by timeout. Is anything wrong?

like image 434
miksh Avatar asked Mar 07 '13 14:03

miksh


3 Answers

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.

like image 53
Svet Angelov Avatar answered Nov 11 '22 00:11

Svet Angelov


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}");
}
like image 37
Kapé Avatar answered Nov 11 '22 01:11

Kapé


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

like image 12
Khalid Omar Avatar answered Nov 11 '22 01:11

Khalid Omar