Why does the HttpWebRequest asynchronous method BeginGetResponse block?

Hi Experts,

In the Microsoft .NET (Version 2.0) framework documentation there is sample code for the asynchronous HttpWebRequest.BeginGetResponse method. (That code is also below with some extra console writes and one extra check for null.) If you run this example code it appears to work just fine if you leave the www.contoso.com url intact. Here are some other tests to try:

1) change the url to a valid IP address and it returns the page html as expected.
2) change the url to a valid host but invalid page so you get a 404 error and it shows the error as expected.
3) change the url to a non-existant web site and it returns a status of NameResolutionFailure...eg the remote name could not be resolved...again expected results.

All of these tests give correct responses. But the subtle problem is the BeginGetResponse() method blocks when attempting a DNS resolution that fails which is test case 3 above.

To see the problem just copy the code below. When you try test 3 with a non-existent domain you will see about a 5 second delay between seeing the 2 console write messages that are inserted before and after the BeginGetResponse() method...clearly a block condition. You can only do the test once per non-existent domain since that response gets cached somewhere. So you have to constantly change the url to more invalid domains each time you want to run the test to see the problem.

Here is a little more explanation:
In the code below there are two extra Console.Writeline() statements inserted just before and just after the HttpWebRequest.BeginGetResponse() method call. If you watch the output console window in the three tests above you will notice that for test number 3 the BeginGetResponse() method call does not return immediately. There is about a 4-5 second delay between those two messages which doesn't occur for tests 1 and 2. The test 3 problem is occurring during DNS name resolution when the name resolution will fail. The IP address and valid host tests 1 and 2 will not cause the method to block...(I originally thought it was just happening faster so that it appeared not to block but I put in a wait loop where the allDone.WaitOne() is with console writes to verify that tests 1 and 2 did not in fact block.)

I then used Lutz Roeders Reflector app to look at the BeginGetResponse() method logic in the dotNET framework and really got lost in what they are doing. So I am turning to you guys now. And the question is why is this asynchronous call blocking and is there a work around? Is this an MS bug? This code was modified between versions 1.1 and 2.0 of the dotNet frameworks according to some posts I found about thread pool exhaustion which exsited in 1.1.


using System;
using System.Net;
using System.IO;
using System.Text;
using System.Threading;

namespace AsyncHttpRequestTest
{

    public class RequestState
    {
        // This class stores the State of the request.
        const int BUFFER_SIZE = 1024;
        public StringBuilder requestData;
        public byte[] BufferRead;
        public HttpWebRequest request;
        public HttpWebResponse response;
        public Stream streamResponse;
        public RequestState()
        {
            BufferRead = new byte[BUFFER_SIZE];
            requestData = new StringBuilder("");
            request = null;
            streamResponse = null;
        }
    }

    class HttpWebRequest_BeginGetResponse
    {
        public static ManualResetEvent allDone = new ManualResetEvent(false);
        const int BUFFER_SIZE = 1024;
        const int DefaultTimeout = 2 * 60 * 1000; // 2 minutes timeout

        // Abort the request if the timer fires.
        private static void TimeoutCallback(object state, bool timedOut)
        {
            if (timedOut)
            {
                HttpWebRequest request = state as HttpWebRequest;
                if (request != null)
                {
                    request.Abort();
                }
            }
        }

        static void Main(string[] args)
        {

            try
            {
                // Create a HttpWebrequest object to the desired URL.
                HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create("http://www.siw09e8ihqwe9.com");


                /**
                  * If you are behind a firewall and you do not have your browser proxy setup
                  * you need to use the following proxy creation code.

                    // Create a proxy object.
                    WebProxy myProxy = new WebProxy();

                    // Associate a new Uri object to the _wProxy object, using the proxy address
                    // selected by the user.
                    myProxy.Address = new Uri("http://myproxy");
       
       
                    // Finally, initialize the Web request object proxy property with the _wProxy
                    // object.
                    myHttpWebRequest.Proxy=myProxy;
                  ***/

                // Create an instance of the RequestState and assign the previous myHttpWebRequest
                // object to its request field.  
                RequestState myRequestState = new RequestState();
                myRequestState.request = myHttpWebRequest;

Console.WriteLine("\nBefore BeginGetResponse");

                // Start the asynchronous request.
                IAsyncResult result =
                  (IAsyncResult)myHttpWebRequest.BeginGetResponse(new AsyncCallback(RespCallback), myRequestState);

Console.WriteLine("\nBefore RegisterWaitForSingleObject");

                // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
                ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), myHttpWebRequest, DefaultTimeout, true);

                // The response came in the allowed time. The work processing will happen in the
                // callback function.
                allDone.WaitOne();

                // Release the HttpWebResponse resource.
                if (myRequestState.response != null)
                    myRequestState.response.Close();
            }
            catch (WebException e)
            {
                Console.WriteLine("\nMain Exception raised!");
                Console.WriteLine("\nMessage:{0}", e.Message);
                Console.WriteLine("\nStatus:{0}", e.Status);
                Console.WriteLine("Press any key to continue..........");
            }
            catch (Exception e)
            {
                Console.WriteLine("\nMain Exception raised!");
                Console.WriteLine("Source :{0} ", e.Source);
                Console.WriteLine("Message :{0} ", e.Message);
                Console.WriteLine("Press any key to continue..........");
                Console.Read();
            }
        }

        private static void RespCallback(IAsyncResult asynchronousResult)
        {
            try
            {
                // State of request is asynchronous.
                RequestState myRequestState = (RequestState)asynchronousResult.AsyncState;
                HttpWebRequest myHttpWebRequest = myRequestState.request;
                myRequestState.response = (HttpWebResponse)myHttpWebRequest.EndGetResponse(asynchronousResult);

                // Read the response into a Stream object.
                Stream responseStream = myRequestState.response.GetResponseStream();
                myRequestState.streamResponse = responseStream;

                // Begin the Reading of the contents of the HTML page and print it to the console.
                IAsyncResult asynchronousInputRead = responseStream.BeginRead(myRequestState.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), myRequestState);
                return;
            }
            catch (WebException e)
            {
                Console.WriteLine("\nRespCallback Exception raised!");
                Console.WriteLine("Message:{0}", e.Message);
                Console.WriteLine("Status:{0}", e.Status);
            }
            allDone.Set();
        }

        private static void ReadCallBack(IAsyncResult asyncResult)
        {
            try
            {
                RequestState myRequestState = (RequestState)asyncResult.AsyncState;
                Stream responseStream = myRequestState.streamResponse;
                int read = responseStream.EndRead(asyncResult);
                // Read the HTML page and then print it to the console.
                if (read > 0)
                {
                    myRequestState.requestData.Append(Encoding.ASCII.GetString(myRequestState.BufferRead, 0, read));
                    IAsyncResult asynchronousResult = responseStream.BeginRead(myRequestState.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), myRequestState);
                    return;
                }
                else
                {
                    Console.WriteLine("\nThe contents of the Html page are : ");
                    if (myRequestState.requestData.Length > 1)
                    {
                        string stringContent;
                        stringContent = myRequestState.requestData.ToString();
                        Console.WriteLine(stringContent);
                    }
                    Console.WriteLine("Press any key to continue..........");
                    Console.ReadLine();

                    responseStream.Close();
                }

            }
            catch (WebException e)
            {
                Console.WriteLine("\nReadCallBack Exception raised!");
                Console.WriteLine("Message:{0}", e.Message);
                Console.WriteLine("Status:{0}", e.Status);
            }
            allDone.Set();
        }
    }
}


Thanks for any help and suggestions,
Gery
GDorazioAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

GDorazioAuthor Commented:
Further research and a discussion with a person from Microsoft confirmed this to be a bug. When the specific situation described above occurs, namely, that calling the BeginGetResponse does not return immediately when the domain is invalid, the thread 'blocks' until a DNS response occurs. (Actually, a review of the code using Roeders Reflector reveals it just runs faster when a successful DNS name resolution occurs but the code that executes after name resolution appears to be properly designed for asynchronous behavior.)

A work around for this situation in our case is to do an independent test for DNS name resolution before calling the BeginGetResponse method. This will insure a good program design where all the asychronous threads will behave as expected.

Cheers and hope this helps someone.

Gery

BooModCommented:
Closed, 500 points refunded.
BooMod
Special Ops Mod

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Web Development Software

From novice to tech pro — start learning today.