Перейти к содержанию
Educora
Продвинутый24 мин10 / 16

async/await и небольшой консольный проект

Выполняй работу с ожиданием, не замораживая программу, с помощью `Task`, `async` и `await`, а затем объедини всё изученное в одном проекте — отчёте по журналу оценок.

Проверь себя
В этом уроке ты узнаешь
  • Объяснять, зачем нужен асинхронный код
  • Писать метод async Task<T> и ожидать его с помощью await
  • Ожидать несколько задач одновременно с помощью Task.WhenAll
  • Объединять записи, коллекции, LINQ и проверки в небольшой программе

Утром ты завариваешь чай. Ты не стоишь у чайника, пока он не закипит, — в это время режешь хлеб и накрываешь на стол. Программы тоже часто ждут: чтения файла, ответа сервера, базы данных. Чтобы программа не «зависала» во время ожидания, в C# пишут асинхронный код — с ключевыми словами async и await.

Task, async и await

Определение
Task

Объект, представляющий работу, которая завершится в будущем. Task — работа без результата, а Task<T> — работа, которая в итоге даст результат типа T: например, Task<string> вернёт строку.

В заголовке асинхронного метода пишут async, он возвращает Task или Task<T>, а его имя по традиции заканчивается на Async. Внутри метода await означает «дождись окончания этой работы, но пока никому не мешай». Task.Delay(500) имитирует ожидание в 500 миллисекунд — в настоящей программе на его месте было бы чтение файла или запрос в интернет. В операторах верхнего уровня await можно писать напрямую.

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!";
}
Ожидаемый результат
Ordering tea...
The kettle is boiling...
Tea is ready!

Настоящая сила асинхронности видна, когда нужно дождаться сразу нескольких дел. Если вызвать метод без await, он запустится и вернёт Task. Затем Task.WhenAll ждёт завершения всех задач вместе и отдаёт их результаты в массиве в порядке запуска. Два дела по полсекунды каждое завершаются примерно за полсекунды, а не за целую секунду.

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";
}
Ожидаемый результат
Started: tea
Started: toast
tea ready + toast ready
Faster than one by one: True
Stopwatch измеряет прошедшее время: оба дела вместе заняли меньше секунды.

Проект: отчёт по журналу оценок

Теперь объединим всё, что ты изучил в курсе. Учитель хранит баллы учеников в виде строк формата Имя;Балл. Программа должна загрузить данные, пропустить ошибочные строки, выставить каждому ученику оценку и вывести короткий отчёт. Разобьём работу на шаги:

  1. 1
    Модель данных

    Описываем ученика однострочной записью record Student(string Name, int Score);.

  2. 2
    Асинхронная загрузка

    Метод LoadLinesAsync возвращает строки. Здесь мы имитируем файл с помощью Task.Delay; в настоящей программе было бы await File.ReadAllLinesAsync("scores.txt").

  3. 3
    Проверка

    Каждую строку делим методом Split(';'), а балл проверяем через int.TryParse — ошибочная строка не останавливает программу.

  4. 4
    Анализ и отчёт

    С помощью LINQ сортируем и находим средний балл, выражением switch выставляем оценки, а интерполяцией выводим аккуратную таблицу.

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);
Ожидаемый результат
Skipped bad line: 'Leyla;abc'
Loaded 4 students
Часть 1: загрузка и проверка
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);
Ожидаемый результат
Aysel   95  A
Nigar   91  A
Murad   79  C
Elvin   45  F
Average: 77.5
Passed: 3 of 4
Часть 2: отчёт по загруженным ученикам

Объедини обе части в одном файле app.cs: код отчёта поставь после кода загрузки, а record Student оставь один раз в конце файла. Затем развивай проект самостоятельно:

  • Читай данные из настоящего файла: await File.ReadAllLinesAsync("scores.txt").
  • Спрашивай у пользователя нового ученика через Console.ReadLine().
  • Посчитай, сколько учеников получили каждую оценку, с помощью GroupBy(s => Grade(s.Score)).
  • Сохрани отчёт в файл с помощью await File.WriteAllTextAsync("report.txt", text).

Главное

  • Асинхронный код не замораживает программу во время ожидания.
  • Метод async возвращает Task или Task<T>, а его имя заканчивается на Async.
  • await ожидает результат задачи; если его забыть, будет предупреждение CS4014.
  • Task.WhenAll ожидает несколько задач вместе и экономит время.
  • Настоящая программа строится из небольших частей: модель (record), загрузка, проверка, анализ (LINQ) и отчёт.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что означает Task<int>?