Edit: I did not end up doing this approach as per the discussion with Stephen Cleary. If you are interested in how I did it differently, take a look at my answer below.
I'm looking for a way to asynchronously read from a NetworkStream with a timeout. Of course, the problem is that there is no way to cancel a ReadAsync() on NetworkStream as it just ignores the CancellationToken. I read an answer which suggested closing the stream on Token cancellation, but in my case that is not an option as the Tcp connection has to remain open. So I came up with the following code, but I was wondering if this is any better than doing a
Task.Run(() => stream.Read(buffer, offset, count)
and just blocking a thread.
public static class TcpStreamExtension
{
public static async Task<int> ReadAsyncWithTimeout(this NetworkStream stream, byte[] buffer, int offset, int count)
{
CancellationTokenSource cts = new CancellationTokenSource();
bool keepTrying = true;
Timer timer = new Timer(stream.ReadTimeout);
timer.Elapsed += new ElapsedEventHandler((sender, args) => stopTrying(sender, args, cts, out keepTrying));
timer.Start();
try
{
if (stream.CanRead)
{
while (true)
{
if (stream.DataAvailable)
{
return await stream.ReadAsync(buffer, offset, count, cts.Token).ConfigureAwait(false);
}
if (keepTrying)
{
await Task.Delay(300, cts.Token).ConfigureAwait(false);
}
else
{
cts.Dispose();
timer.Dispose();
throw new IOException();
}
}
}
}
catch (TaskCanceledException tce)
{
// do nothing
}
finally
{
cts.Dispose();
timer.Dispose();
}
if (stream.DataAvailable)
{
return await stream.ReadAsync(buffer, offset, count).ConfigureAwait(false);
}
throw new IOException();
}
private static void stopTrying(object sender, ElapsedEventArgs args, CancellationTokenSource cts, out bool keepTrying)
{
keepTrying = false;
cts.Cancel();
}
}
The application has to potentially be able to communicate with several thousand endpoints and I wanted to create it in a way that it won't block a bunch of threads as most of the work it does is IO. Also, the case of timing out should be