Azure App Service
Async/Await
Azure troubleshooting
asynchronous programming
cloud computing issues

Async/Await not running in Azure App Service

Master System Design with Codemia

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

Introduction

When async and await appear to stop working after deployment to Azure App Service, the platform is usually exposing existing blocking patterns in your code. The issue often presents as slow requests, hanging endpoints, or apparent synchronous behavior under load. A solid fix requires checking call chains, thread usage, and runtime diagnostics rather than only changing one controller method.

Why It Works Locally But Fails In App Service

Local testing often has low concurrency and fast dependencies, so blocking calls are less visible. In App Service, request concurrency is higher and thread pool starvation appears quickly if any request path uses blocking waits.

Common patterns that cause this:

  • calling .Result or .Wait() on tasks,
  • wrapping I O code inside Task.Run instead of using native async APIs,
  • long synchronous startup work that delays request threads,
  • hidden blocking inside third party SDK calls.

In other words, await can be present in source code while important parts of execution are still effectively synchronous.

Start With A Minimal Correct Pattern

A request path should stay asynchronous from controller to network or database layer.

csharp
1using Microsoft.AspNetCore.Mvc;
2
3[ApiController]
4[Route("api/work")]
5public class WorkController : ControllerBase
6{
7    private readonly HttpClient _httpClient;
8
9    public WorkController(IHttpClientFactory factory)
10    {
11        _httpClient = factory.CreateClient();
12    }
13
14    [HttpGet]
15    public async Task<IActionResult> Get(CancellationToken ct)
16    {
17        using var response = await _httpClient.GetAsync("https://example.org", ct);
18        response.EnsureSuccessStatusCode();
19        var body = await response.Content.ReadAsStringAsync(ct);
20        return Ok(new { length = body.Length });
21    }
22}

This path has no sync wait and no unnecessary thread hopping.

Detect Sync Over Async Quickly

A practical diagnostic pass usually finds problems fast.

  1. Search for .Result, .Wait(), and .GetAwaiter().GetResult().
  2. Confirm that data access library calls are truly async methods.
  3. Add timing logs around each awaited external call.
  4. Capture thread pool counters during load tests.

Use App Service application logs and distributed tracing to compare request timeline locally versus cloud. If one awaited call consistently blocks for long intervals, inspect that dependency first.

App Service Deployment Considerations

Async bugs are often amplified by deployment configuration.

  • Set realistic request timeout and outbound dependency timeout values.
  • Ensure connection pooling is configured for database and HTTP clients.
  • Avoid creating HttpClient per request.
  • Keep background loops out of request code paths.
  • Scale up or out only after fixing blocking code.

Scaling without removing blocking calls usually increases cost before improving reliability.

Background Work Should Not Block Requests

If you need long running operations, queue the work and return quickly from the API.

csharp
1public interface IJobQueue
2{
3    ValueTask QueueAsync(string payload, CancellationToken ct);
4}
5
6[ApiController]
7[Route("api/jobs")]
8public class JobController : ControllerBase
9{
10    private readonly IJobQueue _queue;
11
12    public JobController(IJobQueue queue)
13    {
14        _queue = queue;
15    }
16
17    [HttpPost]
18    public async Task<IActionResult> Submit([FromBody] string payload, CancellationToken ct)
19    {
20        await _queue.QueueAsync(payload, ct);
21        return Accepted();
22    }
23}

This separates user latency from background processing and prevents request thread exhaustion.

About ConfigureAwait

In ASP.NET Core, there is no legacy synchronization context in the same way as classic ASP.NET. That means ConfigureAwait(false) is usually not required to avoid deadlocks, though it can still be used for library code conventions. The bigger risk is still blocking waits, not missing ConfigureAwait.

Common Pitfalls

  • Assuming Azure changed async semantics when code contains sync waits.
  • Using Task.Run around I O and expecting better scalability.
  • Creating many short lived HttpClient instances and exhausting sockets.
  • Ignoring cancellation tokens in long network calls.
  • Treating scale out as a replacement for async correctness.

Summary

  • App Service usually reveals existing sync over async issues under higher concurrency.
  • Remove blocking waits such as .Result and .Wait() from request paths.
  • Keep controller to dependency call chains fully asynchronous.
  • Use logs and thread pool metrics to pinpoint blocking boundaries.
  • Separate long running work from request handling for predictable latency.

Course illustration
Course illustration

All Rights Reserved.