Is this approach better than just firing stream.Read() in a Task.Run?

Viewed 445

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

3 Answers
Related