HttpWebRequest
HttpWebResponse
asynchronous programming
BeginGetResponse
.NET

HttpWebRequest.BeginGetResponse blocks; What is the correct way to get HttpWebResponse asynchronously?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

HttpWebRequest.BeginGetResponse belongs to the old APM pattern built around Begin... and End... methods. It can still be used asynchronously, but many implementations accidentally block because they mix it with synchronous waiting, synchronous request-stream writes, or UI-thread marshaling. In modern .NET code, the correct answer is usually to use await with HttpClient, or at least with GetResponseAsync, instead of writing new code around BeginGetResponse.

Why BeginGetResponse Seems to Block

Calling BeginGetResponse does not guarantee your overall code path is non-blocking. Common reasons it still appears to block include:

  • waiting on the returned IAsyncResult with AsyncWaitHandle.WaitOne()
  • calling EndGetResponse on the initiating thread too early
  • writing the request body synchronously before reading the response
  • blocking the UI thread while the callback tries to marshal back

The API is asynchronous only if the whole workflow is asynchronous.

Preferred Modern Solution: HttpClient

For new code, HttpClient is the cleaner API:

csharp
1using System;
2using System.Net.Http;
3using System.Threading.Tasks;
4
5public static class Demo
6{
7    public static async Task Main()
8    {
9        using var client = new HttpClient();
10        using HttpResponseMessage response = await client.GetAsync("https://example.com");
11
12        response.EnsureSuccessStatusCode();
13        string body = await response.Content.ReadAsStringAsync();
14        Console.WriteLine(body.Length);
15    }
16}

This is easier to read, integrates naturally with async and await, and avoids the callback-heavy structure of the APM pattern.

If You Must Stay on HttpWebRequest

If legacy code forces you to keep HttpWebRequest, use the task-based wrapper when available:

csharp
1using System;
2using System.IO;
3using System.Net;
4using System.Threading.Tasks;
5
6public static class Demo
7{
8    public static async Task Main()
9    {
10        var request = (HttpWebRequest)WebRequest.Create("https://example.com");
11        request.Method = "GET";
12
13        using var response = (HttpWebResponse)await request.GetResponseAsync();
14        using var stream = response.GetResponseStream();
15        using var reader = new StreamReader(stream);
16
17        string body = await reader.ReadToEndAsync();
18        Console.WriteLine(body.Length);
19    }
20}

This preserves the older request type while still using the task-based pattern that composes well with the rest of modern .NET.

Correct APM Usage Pattern

If you are maintaining an older codebase that still uses BeginGetResponse, the important rule is to finish the operation in the callback and avoid synchronous waiting:

csharp
1using System;
2using System.IO;
3using System.Net;
4
5var request = (HttpWebRequest)WebRequest.Create("https://example.com");
6
7request.BeginGetResponse(ar =>
8{
9    try
10    {
11        var originalRequest = (HttpWebRequest)ar.AsyncState!;
12        using var response = (HttpWebResponse)originalRequest.EndGetResponse(ar);
13        using var stream = response.GetResponseStream();
14        using var reader = new StreamReader(stream);
15
16        string body = reader.ReadToEnd();
17        Console.WriteLine(body.Length);
18    }
19    catch (WebException ex)
20    {
21        Console.WriteLine(ex.Message);
22    }
23}, request);

This is asynchronous in the APM sense because you are not blocking the caller waiting for completion. But it is still more awkward and error-prone than await.

Do Not Forget the Request Stream

For POST or PUT requests, the request body matters too. If you write the request stream synchronously and then call BeginGetResponse, the operation can still tie up the calling thread. The request stream should be obtained asynchronously as well, or you should move to HttpClient where the request content handling is simpler.

With HttpWebRequest, a full legacy async flow often needs both:

  • 'BeginGetRequestStream or GetRequestStreamAsync'
  • 'BeginGetResponse or GetResponseAsync'

Doing only half the process asynchronously still leaves synchronous bottlenecks.

Common Pitfalls

The most common pitfall is calling BeginGetResponse and then immediately waiting for it with a manual wait handle. That defeats the purpose of asynchronous I/O.

Another pitfall is assuming the response side is the only part that matters. If the request body is written synchronously, the code may still feel blocked.

Developers also keep using HttpWebRequest in new code because it looks familiar. In most cases, HttpClient is the better abstraction and reduces the chance of subtle blocking bugs.

Finally, if this code runs on a UI thread, avoid .Result, .Wait(), or manual callbacks that depend on the UI message pump. Those patterns are a common source of deadlocks and unresponsive applications.

Summary

  • 'BeginGetResponse is only non-blocking if the surrounding workflow is also fully asynchronous.'
  • For new code, prefer HttpClient with async and await.
  • For legacy HttpWebRequest, prefer GetResponseAsync over new APM code.
  • If you keep BeginGetResponse, complete the work in the callback and do not wait synchronously.
  • For request bodies, make the request-stream step asynchronous too.

Course illustration
Course illustration

All Rights Reserved.