В предыдущей части мы познакомились в общих чертах с тем, что из себя представляют асинхронные методы в C# и как они работают. Сегодня более подробно рассмотрим какие результаты возвращает асинхронный метод и как с этими результатами мы можем работать.
Типы возвращаемых результатов асинхронным методом
Согласно документации Microsoft, асинхронный метод может возвращать значения следующих типов:
void
Task
Task<TResult>
- Любой другой тип, реализующий метод
GetAwaiter
Для работы, обычно бывает достаточным использовать первые три типа данных их мы сегодня и рассмотрим. Итак, в каких случаях какой тип возвращаемых асинхронным методом результатов следует предпочитать и использовать.
void
В этом случае асинхронный метод ничего не возвращает. Например, мы можем создать такой асинхронный метод
private static async void DoWork() { await Task.Delay(3000); Console.WriteLine("Работа выполнена"); }
в этом случае метод ничего не возвращает. Однако не стоит злоупотреблять void
при написании асинхронных методов, даже в том случае, если вы (на первый взгляд) ничего не ожидаете получить в результатах. Вот какие недостатки скрываются за использованием методов async void
:
- исключения, вызываемые в методе
async void
, невозможно перехватывать вне этого метода. - методы
async void
очень трудно тестировать. - методы
async void
могут быть потенциально опасными, если вызывающий объект не ожидает, что они будут асинхронными.
Ну и ещё один момент: к методу async void
нельзя применить оператор await
.
Этот тип данных стоит применять в асинхронных методах только в одном случае — если вы пишете асинхронный обработчик события. В этом случае, использование в качестве возвращаемого результата void
— единственный способ создать обработчик. Во всех остальных случаях следует использовать или Task
или Task<TResult>
.
Task
Этот тип используется в том случае, если в методе отсутствуют инструкции return
или же имеются инструкции return
, которые ничего не возвращают (прерывают выполнение метода). Дело в том, что даже, если выш асинхронный метод не возвращает какого-либо результата — это совсем не означает, что результат выполнения асинхронной задачи отсутствует. Рассмотрим предыдущий пример, но перепишем его следующим образом:
private static async Task DoWork() { await Task.Delay(3000); Console.WriteLine("Работа выполнена"); }
Теперь вызовем метод в нашем приложении:
static async Task Main(string[] args) { Task task = DoWork(); await task; if (task.IsCompleted) { Console.WriteLine("Задача выполнена успешно"); } else { Console.WriteLine("Что-то случилось..."); } }
Как видите, в нашем методе нет return
, то есть метод, по идее, ничего не должен бы возвращать, но он все равно возвращает объект типа Task
в котором содержится полезная информация о задаче. И в данном примере мы проверяем выполнена ли задача или нет. В консоли мы увидим следующее:
Задача выполнена успешно
Использование Task
в методах без инструкции return позволяет проводить отладку асинхронных методов и получать статус задачи.
Task<TResult>
Тип Task<TResult>
используется в том случае, если асинхронный метод должен возвращать результат. При этом, под TResult
может скрываться любой тип данных. Например, попробуем получить главную страницу этого блога, используя асинхронный метод:
using System.Net.Http; private static async Task<string> GetHtmlPage() { HttpClient client= new HttpClient(); HttpResponseMessage result = await client.GetAsync("https://csharp.webdelphi.ru/"); if (result.IsSuccessStatusCode) // { string data = await result.Content.ReadAsStringAsync(); return data; } else { return "Что-то случилось"; } }
Разберем этот пример подробнее. Итак, мы создали объект типа HttpClient
и во второй строке метода запускаем и ожидаем выполнения асинхронной операции:
HttpResponseMessage result = await client.GetAsync("https://csharp.webdelphi.ru/");
Обратите внимание на то, что сам по себе метод GetAsync
возвращает не HttpResponseMessage
(ответ сервера), а Task<HttpResponseMessage>
, но, так как мы применили к задаче оператор await
, то произойдет «магия» и мы получим в переменную result готовый объект типа HttpResponseMessage
. Попробуйте убрать из этой строки await
и получите сообщение об ошибке:
Далее, мы в условном операторе if
проверяем ответ сервера и, если он успешный, то запускаем вторую асинхронную операцию — считываем ответ сервера в виде строки, после чего возвращаем результат метода через return
. Опять же — мы не создаем объект типа Task<string>
, как это, на первый взгляд, должно бы было быть — ответ асинхронной операции автоматически заворачивается в Task
и мы уже в вызывающем методе получаем готовый Task<string>
.
Вот как мог бы выглядеть методы, вызывающий нашу асинхронную операцию:
static async Task Main(string[] args) { Task<string> downloader = GetHtmlPage(); Console.WriteLine("Качаем страничку"); string result = await downloader; Console.WriteLine(result); }
или ещё проще:
static async Task Main(string[] args) { Console.WriteLine("Качаем страничку"); string result = await GetHtmlPage(); Console.WriteLine(result); }
В первом случае мы не применяем оператор await
, предполагая, что результат нам понадобится где-то позднее, поэтому сначала получаем объект задачи. Во втором случае, мы сразу же ожидаем выполнения задачи и, поэтому использовали обычный тип string
для получения результата.
Итого
Мы рассмотрели какие типы данных могут возвращать асинхронные методы в C#. Основные из них — Task
, Task<TResult>
и void
. Использование void не рекомендуется и исключение составляет только ситуация, когда вы пишете асинхронный обработчик события (в этом случае void необходим). За рамками этой части остался вопрос о том, как использовать четвертый вид результата — любой другой тип, реализующий метод GetAwaiter
. Но к этой теме нам предстоит вернуться после того, как разберемся с основными моментами асинхронного программирования в C#