locked
Async HttpWebRequest timeout RRS feed

  • Question

  • I'm writing an app the talks to a WCF service. It uses HttpWebRequest class BeginGetResponse method to kick off the async request. I need to set a timeout on this so that after X seconds the request is aborted. I've found the IAsyncResult's AsyncWaitHandle property returned from BeginGetResponse throws a not supported exception when being accessed so I can't do: ThreadPool.RegisterWaitForSingleObject(requestResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), state, 5000, true); The webrequest class also does not have any timeout properties. What is the best way to implement a timeout on asyn HttpWebRequest?
    Wednesday, October 27, 2010 9:53 PM

Answers

  • You can start with the sample code found here:

    http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse(VS.95).aspx

    And then change the allDone.WaitOne() (just after BeginGetResponse() ) so it has a timeout value:

     

    if (allDone.WaitOne(60000))  
    {  
        // allDone wasn’t signaled by ReadCallback; the request timed out  
        if (myHttpWebRequest1 != null)  
        {  
            myHttpWebRequest1.Abort();  
            // Return an error, etc.  
            return;  
        }  
    }  
     
    // Request succeeded before the timeout 

    Unfortunately the sample code omitted the definition of allDone (a waithandle):
    http://msdn.microsoft.com/en-us/library/57deze2z(v=VS.95).aspx 

    Hope this helps,
    Mark

     

    Monday, March 14, 2011 6:15 PM
  • Hi Mark thanks a lot for the response!

    My code is as follows:


             public static void Get(Uri requestUri, HttpResponseReceived httpResponseReceivedCallback, ICredentials credentials, object userState, bool getResponseAsString = truebool getResponseAsBytes = false
            { 
                ThreadPool.QueueUserWorkItem(state => 
                                                 { 
                                                     var httpWebRequest = (HttpWebRequest) WebRequest.Create(requestUri); 
     
                                                     httpWebRequest.Method = "GET"
                                                     httpWebRequest.Credentials = credentials; 
     
                                                     var httpClientRequestState = new JsonHttpClientRequestState(null, userState, httpResponseReceivedCallback, httpWebRequest, getResponseAsString, getResponseAsBytes); 
     
                                                     httpWebRequest.BeginGetResponse(ResponseReceived, httpClientRequestState); 
     
                                                     if (!httpClientRequestState.TimeoutResetEvent.WaitOne(10000)) 
                                                     { 
                                                         if (httpClientRequestState.HttpWebRequest != null
                                                         { 
                                                             httpWebRequest.Abort(); 
                                                         } 
                                                     } 
                                                 }); 
            } 
     
            private static void ResponseReceived(IAsyncResult asyncResult) 
            { 
                var httpClientRequestState = asyncResult.AsyncState as JsonHttpClientRequestState; 
     
                Debug.Assert(httpClientRequestState != null"httpClientRequestState cannot be null. Fatal error."); 
     
                try 
                { 
                    if (httpClientRequestState != null
                    { 
                        httpClientRequestState.TimeoutResetEvent.Set(); 
     
                        var webResponse = (HttpWebResponse)httpClientRequestState.HttpWebRequest.EndGetResponse(asyncResult); 
     
                        var responseStream = webResponse.GetResponseStream(); 
     
                        if (responseStream != null
                        { 
                            var streamReader = new StreamReader(responseStream); 
     
                            string responseBody = null
     
                            if (httpClientRequestState.GetResponseAsString) 
                            { 
                                responseBody = streamReader.ReadToEnd(); 
                            } 
     
                            byte[] responseBodyBytes = null
     
                            if (httpClientRequestState.GetResponseAsBytes) 
                            { 
                                responseBodyBytes = ReadToEnd(streamReader.BaseStream); 
                            } 
     
                            var httpStatusCode = (int)webResponse.StatusCode; 
     
                            if(httpClientRequestState.HttpResponseReceivedCallback != null
                            { 
                                httpClientRequestState.HttpResponseReceivedCallback(httpStatusCode, responseBody, httpClientRequestState.UserState, responseBodyBytes); 
                            } 
     
                            responseStream.Dispose(); 
                            streamReader.Dispose(); 
                        } 
                    } 
                } 
                catch (Exception exception) 
                { 
                    if (httpClientRequestState != null
                    { 
                        if (httpClientRequestState.HttpResponseReceivedCallback != null
                        { 
                            httpClientRequestState.HttpResponseReceivedCallback(-1, null, httpClientRequestState.UserState, 
                                                                                null); 
                        } 
                    } 
     
                    XbmcJsonRpcExceptionUtilities.MakeXbmcJsonRpcException(exception); 
                } 
            }


    This is working perfectly now so thanks again (Posted the code so others can benefit).


    Tuesday, March 15, 2011 6:55 AM

All replies

  • One way you can do this is with reactive extensions framework. Here's what the code looks like.

    //includes for reactive extensions
    using System.Observable;
    using Microsoft.Phone.Reactive;

    HttpWebRequest request = WebRequest.CreateHttp("http://site/");
    var observableRequest = Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse);

        Observable
         .Timeout(observableRequest.Invoke(), DateTimeOffset.Now.AddMinutes(1))
            .ObserveOnDispatcher() //include if UI accessed from subscribe
            .Subscribe(response => { /* handle web response here*/ }, exception => { /* handle timeout exception here */ });
    Thursday, October 28, 2010 4:55 AM
  • Hey has anyone got any other suggestions? I found a way to do it by adding a System.Threading.Timer to my state object but it feels hacky.
    Sunday, March 13, 2011 7:20 AM
  • You can start with the sample code found here:

    http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse(VS.95).aspx

    And then change the allDone.WaitOne() (just after BeginGetResponse() ) so it has a timeout value:

     

    if (allDone.WaitOne(60000))  
    {  
        // allDone wasn’t signaled by ReadCallback; the request timed out  
        if (myHttpWebRequest1 != null)  
        {  
            myHttpWebRequest1.Abort();  
            // Return an error, etc.  
            return;  
        }  
    }  
     
    // Request succeeded before the timeout 

    Unfortunately the sample code omitted the definition of allDone (a waithandle):
    http://msdn.microsoft.com/en-us/library/57deze2z(v=VS.95).aspx 

    Hope this helps,
    Mark

     

    Monday, March 14, 2011 6:15 PM
  • Thanks for the reply Mark - I'm just curious how the WaitOne works in this situation:

    Some code running on the UI thread calls HttpWebRequest.BeginGetResponse...

    UI Thread:

        DoWebRequest(); 


    public void DoWebRequest() 
        var waitHandle = new WaitHandle() 
     
        var httpWebRequest = new HttpWebRequest(...) 
     
        var state = new asyncState {WaitHandle = waitHandle}; 
     
        httpWebRequest.BeginGetResponse(webRequestCallback, state, ...) 
     
        if(waitHandle.WaitOne(60000))
    {
    httpWebRequest.Abort();
    }
     
     
    public void webRequestCallback(IAsyncResult asyncResult) 
        var state = asyncResult.AsyncState as asyncState; 
     
        // signal state.WaitHandle here as we completed successfully.    


    According to the MSDN documentation I would have just blocked the UI thread for up to 60 seconds waiting for a timeout... Which kind of defeats the purpose of doing this async. I there something I'm missing here? 

    Cheers Tyler

    Monday, March 14, 2011 7:23 PM
  • Hi Tyler,

    No, you aren't missing anything :)
    In this example and for your needs you would need to run DoWebRequest in a worker thread instead of the UI thread.

    - Mark
    Monday, March 14, 2011 8:09 PM
  • Hi Mark thanks a lot for the response!

    My code is as follows:


             public static void Get(Uri requestUri, HttpResponseReceived httpResponseReceivedCallback, ICredentials credentials, object userState, bool getResponseAsString = truebool getResponseAsBytes = false
            { 
                ThreadPool.QueueUserWorkItem(state => 
                                                 { 
                                                     var httpWebRequest = (HttpWebRequest) WebRequest.Create(requestUri); 
     
                                                     httpWebRequest.Method = "GET"
                                                     httpWebRequest.Credentials = credentials; 
     
                                                     var httpClientRequestState = new JsonHttpClientRequestState(null, userState, httpResponseReceivedCallback, httpWebRequest, getResponseAsString, getResponseAsBytes); 
     
                                                     httpWebRequest.BeginGetResponse(ResponseReceived, httpClientRequestState); 
     
                                                     if (!httpClientRequestState.TimeoutResetEvent.WaitOne(10000)) 
                                                     { 
                                                         if (httpClientRequestState.HttpWebRequest != null
                                                         { 
                                                             httpWebRequest.Abort(); 
                                                         } 
                                                     } 
                                                 }); 
            } 
     
            private static void ResponseReceived(IAsyncResult asyncResult) 
            { 
                var httpClientRequestState = asyncResult.AsyncState as JsonHttpClientRequestState; 
     
                Debug.Assert(httpClientRequestState != null"httpClientRequestState cannot be null. Fatal error."); 
     
                try 
                { 
                    if (httpClientRequestState != null
                    { 
                        httpClientRequestState.TimeoutResetEvent.Set(); 
     
                        var webResponse = (HttpWebResponse)httpClientRequestState.HttpWebRequest.EndGetResponse(asyncResult); 
     
                        var responseStream = webResponse.GetResponseStream(); 
     
                        if (responseStream != null
                        { 
                            var streamReader = new StreamReader(responseStream); 
     
                            string responseBody = null
     
                            if (httpClientRequestState.GetResponseAsString) 
                            { 
                                responseBody = streamReader.ReadToEnd(); 
                            } 
     
                            byte[] responseBodyBytes = null
     
                            if (httpClientRequestState.GetResponseAsBytes) 
                            { 
                                responseBodyBytes = ReadToEnd(streamReader.BaseStream); 
                            } 
     
                            var httpStatusCode = (int)webResponse.StatusCode; 
     
                            if(httpClientRequestState.HttpResponseReceivedCallback != null
                            { 
                                httpClientRequestState.HttpResponseReceivedCallback(httpStatusCode, responseBody, httpClientRequestState.UserState, responseBodyBytes); 
                            } 
     
                            responseStream.Dispose(); 
                            streamReader.Dispose(); 
                        } 
                    } 
                } 
                catch (Exception exception) 
                { 
                    if (httpClientRequestState != null
                    { 
                        if (httpClientRequestState.HttpResponseReceivedCallback != null
                        { 
                            httpClientRequestState.HttpResponseReceivedCallback(-1, null, httpClientRequestState.UserState, 
                                                                                null); 
                        } 
                    } 
     
                    XbmcJsonRpcExceptionUtilities.MakeXbmcJsonRpcException(exception); 
                } 
            }


    This is working perfectly now so thanks again (Posted the code so others can benefit).


    Tuesday, March 15, 2011 6:55 AM
  • Any possibility of getting the full class code for JsonHttpClientRequestState?
    Wednesday, September 7, 2011 6:22 PM
  • Digital Reality - here are a few things to note about the example code that you provided (and thanks, it saved me a lot of time!):

    (1) You are missing a try-catch in your QueueUserWorkItem call - any exceptions thrown by BeginGetResponse will be unhandled otherwise.
    (2) I couldn't get your code to work in a Windows Phone XNA project without tweaking the QueueUserWorkItem call because it doesn't have a state parameter.  I am using "ThreadPool.QueueUserWorkItem(new WaitCallback(target => {" instead.
    (3) To clarify your use of "httpClientRequestState.TimeoutResetEvent" for the WaitOne() call, I am setting it to "new AutoResetEvent(false)", which works fine.
    (4) It should also be noted that some of the MSDN examples (for Windows) use a ManualResetEvent for WaitOne and declares it static.  Declaring the wait event as static isn't a good idea if the developer wants to "get" multiple files at once (because they'd all be sharing the same reset event).  Your solution is one way to avoid that problem (putting it in the state object).  I am just creating the AutoResetEvent on the call stack, which works OK as long as you have WaitOne block the thread (otherwise you need to keep track of the wait event).
    (5) You could use "using (...) { ... }" on the streams to automatically close them during successful operations and also when exceptions occur

    Good luck,
    Dan Colasanti
    www.improvisoft.com
    www.improvisoft.com/blog
    twitter: @dancolasanti, @improvisoft
    Tuesday, September 20, 2011 6:51 AM
  • If i understand your code correctly, Digital Reality, it means that if you want to simultaneously perform, say, 15 requests (i.e. calling the Get() method 15 times in a loop, for instance), then 15 thread pool threads will be blocked waiting for TimeoutResetEvent. As far as i know, blocking thread pool threads in this way is almost always a quite bad idea. I need to implement a similar timeout mechanism, but i need something more efficient, since in my case there will definitely be multiple simultaneous requests. I was hoping i could use ThreadPool.RegisterWaitForSingleObject(), but as you said in the first post, the framework doesn't seem to allow you to use it properly.

    Any other suggestions ?
    Tuesday, March 27, 2012 7:37 AM