C#
async
WebMethod
jQuery
ajax

Calling a C async WebMethod using jQuery's .ajax hangs endlessly

Master System Design with Codemia

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

Introduction

When a C# async WebMethod is called from jQuery's $.ajax() and the request hangs indefinitely, the cause is almost always a deadlock caused by .Result or .Wait() on an async call within the ASP.NET synchronization context. ASP.NET WebForms uses a single-threaded SynchronizationContext that requires the request thread to process continuations. If that thread is blocked waiting for the async result, the continuation can never run, creating a classic deadlock. The fix is to use async/await all the way through or use ConfigureAwait(false) on internal async calls.

The Deadlock Pattern

csharp
1// WRONG: This causes a deadlock in ASP.NET WebForms
2[WebMethod]
3public static string GetData()
4{
5    // .Result blocks the current thread (the ASP.NET request thread)
6    var result = GetDataAsync().Result;  // DEADLOCK!
7    return result;
8}
9
10private static async Task<string> GetDataAsync()
11{
12    // This await needs the ASP.NET synchronization context thread
13    // But that thread is blocked by .Result above
14    await Task.Delay(1000);
15    return "Hello from async";
16}

The ASP.NET SynchronizationContext marshals continuations back to the original request thread. When .Result blocks that thread, the await continuation cannot execute, and both are stuck waiting for each other.

The Fix: Async All the Way

csharp
1// CORRECT: Use async/await throughout
2[WebMethod]
3public static async Task<string> GetData()
4{
5    var result = await GetDataAsync();
6    return result;
7}
8
9private static async Task<string> GetDataAsync()
10{
11    await Task.Delay(1000);
12    var data = await FetchFromDatabaseAsync();
13    return data;
14}
15
16private static async Task<string> FetchFromDatabaseAsync()
17{
18    // Simulating async database call
19    await Task.Delay(500);
20    return "Data from DB";
21}

Making the WebMethod itself async Task<string> allows ASP.NET to release the request thread during await, preventing the deadlock. The response is sent when the Task completes.

The jQuery Side

javascript
1// jQuery AJAX call
2$.ajax({
3    type: "POST",
4    url: "MyPage.aspx/GetData",
5    contentType: "application/json; charset=utf-8",
6    dataType: "json",
7    success: function (response) {
8        // ASP.NET WebMethods wrap the result in .d
9        console.log(response.d);
10    },
11    error: function (xhr, status, error) {
12        console.error("Error:", status, error);
13    }
14});
15
16// With async/await (modern JavaScript)
17async function fetchData() {
18    try {
19        const response = await $.ajax({
20            type: "POST",
21            url: "MyPage.aspx/GetData",
22            contentType: "application/json; charset=utf-8",
23            dataType: "json"
24        });
25        console.log(response.d);
26    } catch (error) {
27        console.error("Failed:", error);
28    }
29}

The jQuery side is rarely the problem — the hang originates on the server. The AJAX request simply waits until the server responds (which it never does when deadlocked).

Fix with ConfigureAwait(false)

csharp
1// Alternative fix: ConfigureAwait(false) in library code
2[WebMethod]
3public static string GetData()
4{
5    // Still synchronous WebMethod, but inner calls don't capture context
6    var result = GetDataAsync().Result;  // Works now (but not ideal)
7    return result;
8}
9
10private static async Task<string> GetDataAsync()
11{
12    // ConfigureAwait(false) tells the await to NOT capture the
13    // synchronization context — it can resume on any thread
14    await Task.Delay(1000).ConfigureAwait(false);
15    return "Hello from async";
16}

ConfigureAwait(false) prevents the continuation from trying to marshal back to the ASP.NET thread. This avoids the deadlock but is a workaround — making the WebMethod fully async is the proper solution.

Passing Parameters from AJAX

javascript
1// Sending parameters to the WebMethod
2$.ajax({
3    type: "POST",
4    url: "MyPage.aspx/GetUserData",
5    data: JSON.stringify({ userId: 42 }),
6    contentType: "application/json; charset=utf-8",
7    dataType: "json",
8    success: function (response) {
9        console.log(response.d);
10    }
11});
csharp
1[WebMethod]
2public static async Task<string> GetUserData(int userId)
3{
4    // Parameter names must match the JSON keys exactly
5    var user = await FetchUserAsync(userId);
6    return user.Name;
7}

Parameter names in the JSON payload must match the WebMethod parameter names exactly (case-sensitive in some configurations).

Diagnosing the Hang

csharp
1// Add timeout to detect the deadlock
2[WebMethod]
3public static string GetDataWithTimeout()
4{
5    var task = GetDataAsync();
6
7    // If this times out, you have a deadlock
8    if (!task.Wait(TimeSpan.FromSeconds(5)))
9    {
10        return "ERROR: Operation timed out (possible deadlock)";
11    }
12    return task.Result;
13}
14
15// Better: add logging to trace where execution stops
16[WebMethod]
17public static async Task<string> GetDataDebug()
18{
19    System.Diagnostics.Debug.WriteLine("WebMethod entered");
20    var result = await GetDataAsync();
21    System.Diagnostics.Debug.WriteLine("WebMethod completed");
22    return result;
23}

Common Pitfalls

  • Calling .Result or .Wait() on async code in ASP.NET: This is the primary cause of the deadlock. The ASP.NET synchronization context requires the request thread for continuations, but .Result blocks that thread. Always use async/await from the entry point (the WebMethod) down through all async calls.
  • Forgetting to set contentType to application/json: ASP.NET WebMethods require contentType: "application/json; charset=utf-8" in the AJAX call. Without it, the server returns a 500 error or the method is not invoked at all, which looks like a hang if no error handler is attached.
  • Not making the WebMethod public static: ASP.NET page methods must be public static and decorated with [WebMethod]. An instance method or missing attribute silently fails — the AJAX call receives no response.
  • Using ConfigureAwait(false) as a blanket fix: While ConfigureAwait(false) breaks the deadlock, it means the continuation runs on a thread pool thread without the ASP.NET context. If downstream code accesses HttpContext.Current, it will be null. Only use it in library code that does not need the HTTP context.
  • Missing error handling on the jQuery side: Without an error callback in $.ajax(), failed requests silently disappear. Always add error handling to distinguish between a server deadlock (timeout), a 500 error, and a network failure.

Summary

  • The deadlock occurs when .Result or .Wait() blocks the ASP.NET request thread that async continuations need
  • Fix by making the WebMethod async Task<string> and using await throughout
  • ConfigureAwait(false) is a workaround for library code but loses HttpContext
  • The jQuery AJAX call itself is rarely the cause — the hang is server-side
  • Always set contentType: "application/json" and add error handlers to $.ajax()
  • Use async/await all the way from the WebMethod to the lowest async call

Course illustration
Course illustration

All Rights Reserved.