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
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}");
}
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
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
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.