- Объяснять, зачем нужен асинхронный код
- Писать метод
async Task<T>и ожидать его с помощьюawait - Ожидать несколько задач одновременно с помощью
Task.WhenAll - Объединять записи, коллекции, LINQ и проверки в небольшой программе
Утром ты завариваешь чай. Ты не стоишь у чайника, пока он не закипит, — в это время режешь хлеб и накрываешь на стол. Программы тоже часто ждут: чтения файла, ответа сервера, базы данных. Чтобы программа не «зависала» во время ожидания, в C# пишут асинхронный код — с ключевыми словами async и await.
Task, async и await
Объект, представляющий работу, которая завершится в будущем. Task — работа без результата, а Task<T> — работа, которая в итоге даст результат типа T: например, Task<string> вернёт строку.
В заголовке асинхронного метода пишут async, он возвращает Task или Task<T>, а его имя по традиции заканчивается на Async. Внутри метода await означает «дождись окончания этой работы, но пока никому не мешай». Task.Delay(500) имитирует ожидание в 500 миллисекунд — в настоящей программе на его месте было бы чтение файла или запрос в интернет. В операторах верхнего уровня await можно писать напрямую.
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 ждёт завершения всех задач вместе и отдаёт их результаты в массиве в порядке запуска. Два дела по полсекунды каждое завершаются примерно за полсекунды, а не за целую секунду.
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Модель данных
Описываем ученика однострочной записью
record Student(string Name, int Score);. - 2Асинхронная загрузка
Метод
LoadLinesAsyncвозвращает строки. Здесь мы имитируем файл с помощьюTask.Delay; в настоящей программе было быawait File.ReadAllLinesAsync("scores.txt"). - 3Проверка
Каждую строку делим методом
Split(';'), а балл проверяем черезint.TryParse— ошибочная строка не останавливает программу. - 4Анализ и отчёт
С помощью LINQ сортируем и находим средний балл, выражением switch выставляем оценки, а интерполяцией выводим аккуратную таблицу.
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
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
Объедини обе части в одном файле 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.
Task<int>?