asynchronous programming
C# async await
MVC framework
async methods
C# programming

How to make this asynchronous? async, await - C, MVC

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In ASP.NET MVC, making code asynchronous is mainly about not tying up a request thread while the application waits for I/O. The key idea is simple: if the underlying operation already offers an asynchronous API, let that async boundary flow all the way from the database or HTTP call up to the controller action.

Start with an Async Controller Action

A controller action should return Task<ActionResult> or a related task-based result type when it awaits asynchronous work. The async keyword is only useful if the method actually awaits something that yields control.

csharp
1using System.Net.Http;
2using System.Threading.Tasks;
3using System.Web.Mvc;
4
5public class StatusController : Controller
6{
7    private static readonly HttpClient Http = new HttpClient();
8
9    public async Task<ActionResult> Index()
10    {
11        string body = await Http.GetStringAsync("https://example.com/api/status");
12        return Content(body, "application/json");
13    }
14}

This improves scalability because the request thread can return to the pool while the network call is in progress.

Make the Service Layer Async Too

A controller marked async does not help much if the service or repository layer still blocks internally. The entire I/O path should use asynchronous APIs where possible.

csharp
1using System.Data.Entity;
2using System.Threading.Tasks;
3
4public class ProductService
5{
6    private readonly AppDbContext _db;
7
8    public ProductService(AppDbContext db)
9    {
10        _db = db;
11    }
12
13    public Task<Product> FindAsync(int id)
14    {
15        return _db.Products.SingleOrDefaultAsync(p => p.Id == id);
16    }
17}

Then the controller stays straightforward:

csharp
1public class ProductsController : Controller
2{
3    private readonly ProductService _service;
4
5    public ProductsController(ProductService service)
6    {
7        _service = service;
8    }
9
10    public async Task<ActionResult> Details(int id)
11    {
12        var product = await _service.FindAsync(id);
13        if (product == null)
14        {
15            return HttpNotFound();
16        }
17
18        return View(product);
19    }
20}

That pattern is what people usually mean when they say an MVC flow is truly asynchronous.

Do Not Fake Async with Task.Run

A common misstep is wrapping synchronous I/O in Task.Run and assuming that made the operation scalable.

csharp
1public async Task<ActionResult> BadExample()
2{
3    var result = await Task.Run(() => LegacyRepository.LoadReport());
4    return View(result);
5}

This moves work to another thread, but it does not eliminate the blocking call. For web applications, this often wastes thread-pool capacity rather than improving throughput. The better fix is to adopt a library or data access method that already exposes asynchronous operations.

Async Is Best for I/O, Not for Everything

Use async for waiting-heavy operations such as:

  • database queries
  • HTTP requests
  • file reads and writes
  • queue or broker calls

It is not automatically the right choice for short CPU-bound calculations. If an operation is pure computation, async often adds ceremony without improving latency or server capacity.

Keep Error Handling and Composition Simple

Exceptions from awaited calls are still handled with normal try and catch blocks, which is one reason async and await are easier to maintain than callback-heavy designs.

csharp
1public async Task<ActionResult> Import()
2{
3    try
4    {
5        await _importService.RunAsync();
6        return RedirectToAction("Done");
7    }
8    catch (System.Exception ex)
9    {
10        return Content(ex.Message);
11    }
12}

You can also run independent asynchronous operations concurrently with Task.WhenAll.

csharp
1public async Task<ActionResult> Dashboard()
2{
3    var productsTask = _catalogService.LoadAsync();
4    var usersTask = _userService.LoadAsync();
5
6    await Task.WhenAll(productsTask, usersTask);
7
8    var model = new DashboardViewModel
9    {
10        Products = productsTask.Result,
11        Users = usersTask.Result,
12    };
13
14    return View(model);
15}

This is useful when the operations do not depend on each other.

Common Pitfalls

One pitfall is marking only the controller async while leaving the rest of the stack synchronous. That looks modern in the action signature, but it still blocks under load.

Another pitfall is using .Result or .Wait() inside request code. Those calls reintroduce blocking and can create deadlock risks in some environments.

It is also common to over-apply async. If the operation is simple in-memory work, turning it into a task-based chain does not automatically make the application faster.

Summary

  • In MVC, async is most valuable for I/O-bound work that would otherwise block request threads.
  • Return Task-based action results and await real asynchronous APIs.
  • Propagate async through controllers, services, and repositories.
  • Avoid Task.Run, .Result, and .Wait() as substitutes for native async I/O.
  • Use concurrency helpers such as Task.WhenAll only when operations are independent.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.