Async/Await Deep Dive

Advanced 25 min read Lesson 4 of 6

async/await lets your code run long operations (database calls, file I/O, web requests) without blocking the thread that's running it — crucial for responsive UIs and scalable web servers.

A synchronous vs asynchronous method

// Synchronous — blocks the calling thread until it finishes
public string ReadFileSync(string path)
{
    return File.ReadAllText(path);
}

// Asynchronous — frees the thread while waiting on I/O
public async Task<string> ReadFileAsync(string path)
{
    return await File.ReadAllTextAsync(path);
}


                

The rules

  • An async method returns Task, Task<T>, or void (only for event handlers).
  • await can only be used inside an async method.
  • Async "all the way" — once you go async, calling code should await it too.

Async database call

public async Task<List<Student>> GetStudentsAsync()
{
    return await _context.Students
        .Where(s => s.EnrolledOn.Year == 2024)
        .ToListAsync();
}

// In a Razor Pages handler:
public async Task OnGetAsync()
{
    Students = await _studentService.GetStudentsAsync();
}

Running tasks in parallel

Task<List<Student>> studentsTask = GetStudentsAsync();
Task<List<Course>> coursesTask = GetCoursesAsync();

await Task.WhenAll(studentsTask, coursesTask);

var students = studentsTask.Result;
var courses = coursesTask.Result;

Common mistake: blocking on async code

// DON'T DO THIS — can cause deadlocks
var students = GetStudentsAsync().Result;

// DO THIS instead
var students = await GetStudentsAsync();

Exception handling with async

try
{
    var students = await GetStudentsAsync();
}
catch (SqlException ex)
{
    // handle database-specific failure
}
catch (Exception ex)
{
    // handle anything else
}
Key Takeaway

Async/await makes your application more responsive and scalable by freeing threads during I/O operations.