Skip to content
Educora
Advanced24 min10 / 16

async/await and a small console project

Use `Task`, `async` and `await` to do work that involves waiting without freezing the program, then combine everything from the course in one project — a gradebook report.

Check yourself
In this lesson you will learn
  • Explain why asynchronous code is needed
  • Write an async Task<T> method and wait for it with await
  • Wait for several tasks at once with Task.WhenAll
  • Combine records, collections, LINQ and validation in a small program

In the morning you make tea. You do not stand by the kettle until it boils — meanwhile you slice bread and set the table. Programs wait a lot too: for a file to be read, for a server to answer, for a database. So that a program does not “freeze” while waiting, C# code is written asynchronously, with the keywords async and await.

Task, async and await

Definition
Task

An object that represents work which will finish in the future. Task is work without a result, and Task<T> is work that will eventually produce a result of type T — for example, a Task<string> will give back a string.

An asynchronous method has async in its header, returns Task or Task<T>, and its name traditionally ends with Async. Inside it, await means “wait until this job is done, but do not block anyone else meanwhile”. Task.Delay(500) imitates a 500-millisecond wait — in a real program there would be a file read or an internet request instead. With top-level statements you can write await directly.

C#
Console.WriteLine("Ordering tea...");
string tea = await MakeTeaAsync();
Console.WriteLine(tea);

async Task<string> MakeTeaAsync()
{
    Console.WriteLine("The kettle is boiling...");
    await Task.Delay(500);
    return "Tea is ready!";
}
Expected output
Ordering tea...
The kettle is boiling...
Tea is ready!

The real power of asynchrony shows when you wait for several jobs at once. If you call a method without await, it starts and hands you a Task. Then Task.WhenAll waits for all the tasks together and gives their results in an array, in the order you started them. Two jobs of half a second each finish in about half a second, not in a whole second.

C#
using System.Diagnostics;

var timer = Stopwatch.StartNew();

Task<string> teaTask = PrepareAsync("tea", 500);
Task<string> toastTask = PrepareAsync("toast", 500);
string[] results = await Task.WhenAll(teaTask, toastTask);

Console.WriteLine(string.Join(" + ", results));
Console.WriteLine($"Faster than one by one: {timer.ElapsedMilliseconds < 1000}");

async Task<string> PrepareAsync(string item, int milliseconds)
{
    Console.WriteLine($"Started: {item}");
    await Task.Delay(milliseconds);
    return $"{item} ready";
}
Expected output
Started: tea
Started: toast
tea ready + toast ready
Faster than one by one: True
Stopwatch measures the time that passed: both jobs together took less than a second.

Project: a gradebook report

Now let's combine what you have learned in this course. A teacher keeps students' scores as lines in the format Name;Score. The program must load the data, skip bad lines, give each student a grade and print a short report. We split the work into steps:

  1. 1
    Data model

    We describe a student with the one-line record Student(string Name, int Score);.

  2. 2
    Asynchronous loading

    The LoadLinesAsync method returns the lines. Here we imitate the file with Task.Delay; a real program would use await File.ReadAllLinesAsync("scores.txt").

  3. 3
    Validation

    We split each line with Split(';') and check the score with int.TryParse — a bad line does not stop the program.

  4. 4
    Analysis and report

    We sort and find the average with LINQ, give grades with a switch expression and print a neat table with interpolation.

C#
string[] lines = await LoadLinesAsync();
List<Student> students = [];

foreach (string line in lines)
{
    string[] parts = line.Split(';');
    if (parts.Length == 2 && int.TryParse(parts[1], out int score))
        students.Add(new Student(parts[0], score));
    else
        Console.WriteLine($"Skipped bad line: '{line}'");
}
Console.WriteLine($"Loaded {students.Count} students");

async Task<string[]> LoadLinesAsync()
{
    await Task.Delay(300);
    return ["Aysel;95", "Murad;79", "Leyla;abc", "Elvin;45", "Nigar;91"];
}

record Student(string Name, int Score);
Expected output
Skipped bad line: 'Leyla;abc'
Loaded 4 students
Part 1: loading and validation
C#
using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
List<Student> students = [new("Aysel", 95), new("Murad", 79), new("Elvin", 45), new("Nigar", 91)];

foreach (var s in students.OrderByDescending(s => s.Score))
    Console.WriteLine($"{s.Name,-6}{s.Score,4}  {Grade(s.Score)}");

Console.WriteLine($"Average: {students.Average(s => s.Score):F1}");
Console.WriteLine($"Passed: {students.Count(s => s.Score >= 50)} of {students.Count}");

string Grade(int score) => score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 50 => "D",
    _ => "F"
};

record Student(string Name, int Score);
Expected output
Aysel   95  A
Nigar   91  A
Murad   79  C
Elvin   45  F
Average: 77.5
Passed: 3 of 4
Part 2: the report on the loaded students

Combine both parts in one app.cs file: put the report code after the loading code and keep record Student once at the end of the file. Then grow the project yourself:

  • Read the data from a real file: await File.ReadAllLinesAsync("scores.txt").
  • Ask the user to add a new student with Console.ReadLine().
  • Count how many students got each grade with GroupBy(s => Grade(s.Score)).
  • Save the report to a file with await File.WriteAllTextAsync("report.txt", text).

Key points

  • Asynchronous code does not freeze the program while it waits.
  • An async method returns Task or Task<T>, and its name ends with Async.
  • await waits for a task's result; forgetting it gives warning CS4014.
  • Task.WhenAll waits for several tasks together and saves time.
  • A real program is built from small parts: a model (record), loading, validation, analysis (LINQ) and a report.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
What does Task<int> represent?