1

I want to bound the maximum time it can take for reading a stream. The ReadTimeout property doesn't bound the maximum time, it only applies when no data is received in the ReadTimeout window. How can I make sure that the below code takes no more than N seconds to execute and if it takes more than N seconds, I want to cancel and free up all the underlying resources.

          using (WebResponse response = ExecuteHttpRequest(
                httpRequestInputs.EndpointUrl,
                new HttpMethod(httpRequestInputs.Method),
                httpRequestInputs.Body,
                httpRequestInputs.Headers))
            {
                statusCode = ((HttpWebResponse)response).StatusCode;

                using (var responseStream = response.GetResponseStream())
                {
                    using (var streamReader = new StreamReader(responseStream))
                    {
                        var responseContent = new char[m_maxResponseSizeToBeRead];
                        var bytesRead = streamReader.ReadBlock(responseContent, 0, m_maxResponseSizeToBeRead);
                        if (!streamReader.EndOfStream){
                            isResponseSizeLimitExceeded = true;
                        }
                        else
                        {
                            result = new String(responseContent, 0, bytesRead);
                        }
                    }
                }
            }

Can I wrap this in some task and then cancel that task after N seconds ? Will that free up the resources which are created in the task ?

Harshil Lodhi
  • 7,274
  • 1
  • 33
  • 42
  • What about `HttpWebRequest.Timeout` property? – Evk Feb 15 '18 at 07:18
  • How about running it in a thread, and then in another thread keep track of how long it has been running? If it exceeds the allowed N seconds, then you can just kill the first thread.. – Martin M Feb 15 '18 at 07:25
  • 1
    Don’t wrap IO bound work in a CPU-bound task, but make it properly async, i.e. use ReadBlockAsync instead of ReadBlock and ExecuteHttpRequestAsync (if it’s exists). Then you provide a CancellationToken to each async call created by the CancellationTokenSource(TimeSpan) constructor which you declare once outside your block, which will automatically cancel all subtasks (i.e. the IO reads) if this total timespan is exceeded. – ckuri Feb 15 '18 at 07:29
  • @Evk Timeout only works if the remote server doesn't respond in that time, I want to make sure that the whole operation including reading the whole stream completes in N seconds. – Harshil Lodhi Feb 15 '18 at 07:30
  • @ckuri except `ReadBlockAsync` does not accept CancellationToken. – Evk Feb 15 '18 at 07:31
  • @ckuri I exactly wanted to do what you suggested. Unfortunately the code block I am trying to execute uses ReadBlock and I can't change that because of some reasons. So I want to wrap all that code and execute it and also dispose the resources when it timesout – Harshil Lodhi Feb 15 '18 at 07:31
  • 1
    @MartinM There is no need for a thread, nor is it recommended to just kill off a task when IO access is being done. Most IO methods have async counterparts which allow a graceful cancellation. – ckuri Feb 15 '18 at 07:32
  • So my question can be reframed as Can we execute sync code in an async task and also dispose all the underlying resources when the uber task cancels – Harshil Lodhi Feb 15 '18 at 07:32
  • 1
    Ah, I didn’t see that ReadBlockAsync takes no token. But you can easily cancel it nevertheless by calling `using (ct.Register(() => responsestream.Close())` before your StreamReader using. This will immediately close the stream when the cancellation token‘s time run out and thus stop all IO requests. – ckuri Feb 15 '18 at 07:37

1 Answers1

0

You can easily inherit the StreamReader and add you own functionalities:

public class StreamReaderWithMaxTime : StreamReader
{
    public TimeSpan MaxTime { get; set; } = new TimeSpan(0, 1, 0);
    public DateTime? Start { get; set; }

    public override int ReadBlock(char[] buffer, int index, int count)
    {
        if (Start == null)
            Start = DateTime.Now;

        if (Start.Value.Add(MaxTime) < DateTime.Now)
            throw new Exception("Timeout! Max readtime reached!");

        int result = 0;
        Thread t = new Thread(() => result = base.ReadBlock(buffer, index, count));
        t.Start();
        if (!t.Join(Start.Add(MaxTime) - DateTime.Now))
        {
            t.Abort();
            throw new Exception("Timeout! Max readtime reached!");
        }

        return result;
    }
}
kara
  • 3,205
  • 4
  • 20
  • 34
  • This doesn't solve the problem. What is the first base.ReadBlock takes more than MaxTime ? who will cancel it ? – Harshil Lodhi Feb 15 '18 at 07:52
  • You can capsule it in a thread. Wait for the thread and cancel it, if you want. – kara Feb 15 '18 at 07:55
  • How to TimeOut something: https://stackoverflow.com/questions/2265412/set-timeout-to-an-operation – kara Feb 15 '18 at 08:02
  • Will aborting the thread safely releases the resources that base.readBlock acquires ? – Harshil Lodhi Feb 15 '18 at 08:20
  • 1
    It should only stop the `ReadBlock()`-call. This means you have to handle the `StreamReader` with an using-block or call `Dispose()` manually, in order to release the resources and clear all caching. But this should be done anyway outside of the code above. – kara Feb 15 '18 at 09:51