11

Вопрос такой: Почему надо использовать один способ и почему нельзя использовать другой?

На MSDN пишут:

Этот метод в первую очередь предназначен для использования компилятором, а не для использования в коде приложения.

Но покопавшись в интернете нашёл, что Task.Result исключения оборачивает в AggregateException - это вся разница о которой пишут.

Хотелось бы узнать эту тему глубже. Если будет официальная литература в которой объясняется этот вопрос - буду только рад увидеть её здесь!

Chloroform
  • 1,013
  • Там говорится "This method is intended for compiler use rather than for use in application code." – Qwertiy Mar 03 '18 at 21:22
  • @Qwertiy, неправильный перевод? – Chloroform Mar 03 '18 at 21:24
  • https://translate.google.com/#en/ru/rather%20than - Я бы перевёл как-то так: "Этот метод в первую очередь предназначен для использования компилятором, а не программистом". – Qwertiy Mar 03 '18 at 21:26
  • @Qwertiy, согласен. Поправил вопрос – Chloroform Mar 03 '18 at 21:31

2 Answers2

13

Замечание, на которое вы ссылаетесь, рекомендует не работать с awaiter'ом напрямую, а использовать await.

Конструкция await использует GetAwaiter «под капотом». Но она, в отличие от вашего кода, получает результат асинхронно.

Синхронное получение асинхронного результата опасно, и если не на все 100% представляете себе все тонкости происходящего — вы на верном пути к deadlock'у. В качестве примера, давайте рассмотрим вот такой код:

async Task<int> GetOneAsync()
{
    await Task.Delay(1000);
    return 1;
}
int one = GetOneAsync().GetAwaiter().GetResult();

Если вы выполните это в UI-потоке, возникнет deadlock. Видите, почему? Task, возвращаемый из GetOneAsync, ожидает окончания таймаута, после чего собирается вернуться в UI-поток, чтобы выполнить return 1;. Но GetResult() блокирует UI-поток до получения результата! Таким образом, в функции GetOneAsync возврат в UI-поток никогда не произойдёт.

Прямой вызов GetResult() не приводит к проблемам в некоторых случаях — например, в случае, если Task уже завершён. Вот эту самую проверку компилятор делает, так что его вызов GetResult более безопасен.

Резюмируя: прямая работа с awaiter'ом чревата ошибками. Поэтому рекомендуется не работать с awaiter'ом вручную, а использовать await.


А использование Task.Result настолько же опасно, как и .GetAwaiter().GetResult(). Старайтесь не использовать ни первое, ни второе.

VladD
  • 206,799
  • 4
    Хм.. Вроде просили их между собой сравнить? – Qwertiy Mar 04 '18 at 19:56
  • Спасибо за ответ! Deadlock - штука опасная, но от него можно избавиться добавив ConfigureAwait(false), что не является панацеей, конечно. Саму суть TAP я понимаю. Но всё же, если каким-то образом надо синхронно выполнить операцию, то какой способ выбрать? – Chloroform Mar 04 '18 at 19:59
  • @Qwertiy: Не, ТС думал, что советуют использовать вместо одного другое. А по сути, советуют не пользоваться синхронным ожиданием и не работать с awaiter'ом вручную. – VladD Mar 04 '18 at 21:40
  • Ну может и так, но всё-таки можешь сравнить? – Qwertiy Mar 04 '18 at 21:41
  • 1
    @Qwertiy: Сравнение в последнем абзаце. Краткая выдержка: один хрен. – VladD Mar 04 '18 at 21:41
  • @Qwertiy: Да, есть какие-то различия. Но это всё равно, что обсуждать, нужно ли совать в розетку пальцы левой руки, или правой. Ответ — не суйте пальцы в розетку вообще. – VladD Mar 04 '18 at 21:43
  • @Chloroform: Для начала, ConfigureAwait(false) — не панацея (например, потому, что это заклинание не перебрасывает выполнение в фоновый поток). Во-вторых, если вам нужна синхронная операция, почему бы не воспользоваться синхронным API? Или наоборот, если у вас асинхронное API, почему бы не использовать его асинхронным образом? В общем случае вы не знаете, какие потоки нужно разблокировать, чтобы асинхронное API сработало. И вы не знаете, как устроена асинхронная функция внутри. – VladD Mar 04 '18 at 21:47
  • @VladD, идёт переезд синхронной API на асинхронную, есть некоторые места, где используется приведение к синхронной модели, вот и возник подобный вопрос. Если Task.Result реально использует под капотом Task.GetAwaiter().GetResult() и просто оборачивает исключения - то в принципе у вас достаточный ответ. – Chloroform Mar 05 '18 at 08:10
  • @Chloroform: Если это временный код, до полной асинхронизации, то вполне можно использовать любой из них. Возможны дэдлоки, но это временный баг на время переезда. – VladD Mar 05 '18 at 10:41
  • @Chloroform: Task.Result не вызывает под капотом .GetAwaiter().GetResult(), но они делают внутри практически одно и то же ((1), (2)). – VladD Mar 05 '18 at 11:03
1

Рекомендуют использовать такой подход.

var value = Task.Run(async () => await GetValueAsync()).Result;
RouR
  • 111