Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
{
Thread.Sleep(1_000);
Console.WriteLine("Done with second task!");
});
var task3 = Task.Run(() =>
{
Thread.Sleep(1_000);
Console.WriteLine("Done with third task!");
});
await Task.WhenAll(task1, task2, task3);
}
Запустив программу, вы увидите, что три задачи запускаются в порядке от наименьшего значения, указанного при вызове метода Sleep():
Fun With Async ===>
Done with work!
Void method completed
Done with second task!
Done with third task!
Done with first task!
Completed
Существует также метод WhenAnу(), возвращающий задачу, которая завершилась. Для демонстрации работы WhenAny() измените последнюю строку метода MultipleAwaits() следующим образом:
await Task.WhenAny(task1, task2, task3);
В результате вывод становится таким:
Fun With Async ===>
Done with work!
Void method completed
Done with second task!
Completed
Done with third task!
Done with first task!
Вызов асинхронных методов из неасинхронных методов
В каждом из предшествующих примеров ключевое слово async использовалось для возвращения в поток вызывающего кода, пока выполняется асинхронный метод. В целом ключевое слово await может применяться только в методе, помеченном как async. А что если вы не можете (или не хотите) помечать метод с помощью async?
К счастью, существуют другие способы вызова асинхронных методов. Если вы просто не используете ключевое слово await, тогда код продолжает работу после асинхронного метода, не возвращая управление вызывающему коду. Если вам необходимо ожидать завершения асинхронного метода (что происходит, когда применяется ключевое слово await), то существуют два подхода.
Первый подход предусматривает просто использование свойства Result с методами, возвращающими Task<T>, или метода Wait() с методами, возвращающими Task/Task<T>. (Вспомните, что метод, который возвращает значение, обязан возвращать Task<T>, будучи асинхронным, а метод, не имеющий возвращаемого значения, возвращает Task, когда является асинхронным.) Если метод терпит неудачу, то возвращается AggregateException.
Можете также добавить вызов GetAwaiter().GetResult(), который обеспечивает такой же эффект, как ключевое слово await в асинхронном методе, и распространяет исключения в той же манере, что и async/await. Тем не менее, указанные методы помечены в документации как "не предназначенные для внешнего использования", а это значит, что они могут измениться либо вовсе исчезнуть в какой-то момент в будущем. Вызов GetAwaiter().GetResult() работает как с методами, возвращающими значение, так и с методами без возвращаемого значения.
На заметку! Решение использовать свойство Result или вызов GetAwaiter().GetResult() с Task<T> возлагается полностью на вас, и большинство разработчиков принимают решение, основываясь на обработке исключений. Если ваш метод возвращает Task, тогда вы должны применять вызов GetAwaiter().GetResult() или Wait().
Например, вот как вы могли бы вызывать метод DoWorkAsync():
Console.WriteLine(DoWorkAsync().Result);
Console.WriteLine(DoWorkAsync().GetAwaiter().GetResult());
Чтобы остановить выполнение до тех пор, пока не произойдет возврат из метода с возвращаемым типом void, просто вызовите метод Wait() на объекте Task:
MethodReturningVoidAsync().Wait();
Ожидание с помощью await в блоках catch и finally
В версии C# 6 появилась возможность помещения вызовов await в блоки catch и finally. Для этого сам метод обязан быть async. Указанная возможность демонстрируется в следующем примере кода:
static async Task<string> MethodWithTryCatch()
{
try
{
//Do some work
return "Hello";
}
catch (Exception ex)
{
await LogTheErrors();
throw;
}
finally
{
await DoMagicCleanUp();
}
}
Обобщенные возвращаемые типы в асинхронных методах (нововведение в версии 7.0)
До выхода версии C# 7 возвращаемыми типами методов async были только Task, Task<T> и void. В версии C# 7 доступны дополнительные возвращаемые типы при условии, что они следуют шаблону с ключевым словом async. В качестве конкретного примера можно назвать тип ValueTask. Введите код, подобный показанному ниже:
static async ValueTask<int> ReturnAnInt()
{
await Task.Delay(1_000);
return 5;
}
К типу ValueTask применимы все те же самые правила, что и к типу Task, поскольку ValueTask — это просто объект Task для типов значений, заменяющий собой принудительное размещение объекта в куче.
Локальные функции (нововведение в версии 7.0)
Локальные функции были представлены в главе 4 и использовались в главе 8 с итераторами. Они также могут оказаться полезными для асинхронных методов. Чтобы продемонстрировать преимущество, сначала нужно взглянуть на проблему. Добавьте новый метод по имени MethodWithProblems() со следующим кодом:
static async Task MethodWithProblems(int firstParam, int secondParam)
{
Console.WriteLine("Enter");
await Task.Run(() =>
{
// Вызвать длительно выполняющийся метод
Thread.Sleep(4_000);
Console.WriteLine("First Complete");
// Вызвать еще один длительно выполняющийся метод, который терпит
// неудачу из-за того, что значение второго параметра выходит
// за пределы допустимого диапазона.
Console.WriteLine("Something bad happened");
});
}
Сценарий заключается в том, что вторая длительно выполняющаяся задача терпит неудачу из-за недопустимых входных данных. Вы можете (и должны) добавить в начало метода проверки, но поскольку весь метод является асинхронным, нет никаких гарантий, что такие проверки выполнятся. Было бы лучше, чтобы проверки происходили непосредственно перед выполнением вызываемого кода. В приведенном далее обновленном коде проверки делаются в синхронной манере, после чего закрытая функция выполняется асинхронным образом.
static async Task MethodWithProblemsFixed(int firstParam, int secondParam)
{
Console.WriteLine("Enter");
if (secondParam < 0)
{
Console.WriteLine("Bad data");
return;
}
await actualImplementation();
async Task actualImplementation()
{
await Task.Run(() =>
{
// Вызвать длительно выполняющийся метод
Thread.Sleep(4_000);
Console.WriteLine("First Complete");
// Вызвать еще один длительно выполняющийся метод, который терпит
// неудачу из-за того, что значение