Task.Result - это то же самое, что .GetAwaiter.GetResult ()?

360

Недавно я читал код, в котором используется много асинхронных методов, но иногда их нужно выполнять синхронно. Код делает:

Foo foo = GetFooAsync(...).GetAwaiter().GetResult();

Это то же самое, что

Foo foo = GetFooAsync(...).Result;
Джей Базузи
источник
8
Из документации GetResult: «Этот тип и его члены предназначены для использования компилятором». Другой человек не должен его использовать.
Spender
36
Это называется «синхронизация через асинхронность», и если вы не знаете, как реализована задача, это может быть очень плохой идеей. Во многих случаях он может мгновенно блокироваться (например, метод async/ awaitв MVC)
Марк Гравелл
23
В реальном мире у нас есть конструкторы, у нас нет интерфейсов «без ожидания», которые нам нужно реализовать, и нам везде предоставляются асинхронные методы. Я был бы рад использовать что-то, что просто работает, не задаваясь вопросом, почему это «опасно», «не использовать» или «избегать любой ценой». Каждый раз, когда мне приходится возиться с асинхронным режимом, получается головная боль.
Ларри

Ответы:

193

Довольно много. Однако есть одно небольшое отличие: в случае Taskсбоя GetResult()будет просто сгенерировано исключение, вызванное напрямую, а Task.Resultсгенерируется файл AggregateException. Однако какой смысл использовать любой из них, если он есть async? В 100 раз лучший вариант - использовать await.

Кроме того, вы не должны использовать GetResult(). Он предназначен только для использования компилятором, а не для вас. Но если вы не хотите раздражать AggregateException, используйте это.

Это НЕ.
источник
28
@JayBazuzi Нет, если ваша структура модульного тестирования поддерживает асинхронные модульные тесты, которые, как мне кажется, поддерживают новейшие версии большинства платформ.
svick
15
@JayBazuzi: MSTest, xUnit и NUnit поддерживают async Taskмодульные тесты и уже некоторое время поддерживают их .
Стивен Клири
21
отталкиваясь от 100x - использовать await в 1000 раз хуже, если вы адаптируете старый код, а использование await требует перезаписи.
застрял
13
@AlexZhukovskiy: Я не согласен .
Стивен Клири
29
The 100x better option is to use await.Я ненавижу подобные заявления, если бы я мог дать им пощечину, я бы сделал awaitэто. Но, когда я пытаюсь получить асинхронный код для работы с не-асинхронным кодом , как то , что часто случается со мной много в Xamarin, я в конечном итоге того , чтобы использовать такие вещи , как ContinueWithмного для того , чтобы сделать это не тупиковую интерфейс. Изменить: я знаю, что это старый, но это не облегчает мое разочарование, когда я ищу ответы, в которых говорится об отсутствии альтернатив для ситуаций, когда вы не можете просто использовать await.
Thomas F.
162

Task.GetAwaiter().GetResult()предпочтительнее, Task.Waitи Task.Resultпотому что он распространяет исключения, а не помещает их в файл AggregateException. Однако все три метода могут привести к тупиковой ситуации и нехватке пула потоков. Их следует избегать в пользу async/await.

Цитата ниже объясняет, почему, Task.Waitа Task.Resultне просто содержит поведение распространения исключения Task.GetAwaiter().GetResult()(из-за «очень высокой панели совместимости»).

Как я упоминал ранее, у нас очень высокая планка совместимости, и поэтому мы избежали критических изменений. Таким образом, Task.Waitсохраняет свое первоначальное поведение - всегда обертывание. Однако вы можете оказаться в некоторых сложных ситуациях, когда вам нужно поведение, подобное синхронной блокировке, используемой Task.Wait, но где вы хотите, чтобы исходное исключение распространялось развернутым, а не заключалось в AggregateException. Для этого вы можете напрямую настроить таргетинг на ожидающего задачи. Когда вы пишете « await task;», компилятор переводит это в использование Task.GetAwaiter()метода, который возвращает экземпляр, у которого есть GetResult()метод. При использовании для неисправной Задачи GetResult()будет распространяться исходное исключение (так « await task;» получает свое поведение). Таким образом, вы можете использовать "task.GetAwaiter().GetResult()”, Если вы хотите напрямую вызвать эту логику распространения.

https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/

« GetResult» Фактически означает «проверить задачу на наличие ошибок»

В общем, я изо всех сил стараюсь избегать синхронной блокировки асинхронной задачи. Однако есть несколько ситуаций, когда я нарушаю это правило. В этих редких случаях я предпочитаю метод, GetAwaiter().GetResult()потому что он сохраняет исключения задач, а не помещает их в файл AggregateException.

http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html

Нитин Агарвал
источник
3
Так что в основном Task.GetAwaiter().GetResult() эквивалентно await task. Я предполагаю, что первый вариант используется, когда метод не может быть помечен async(например, конструктором). Это верно? Если да, то он совпадает с верхним ответом @ It'sNotALie
OlegI
6
@OlegI: Task.GetAwaiter().GetResult()более эквивалентен Task.Waitи Task.Result(в том смысле, что все три будут синхронно блокироваться и иметь потенциал для взаимоблокировок), но Task.GetAwaiter().GetResult()имеет поведение распространения исключений задачи ожидания.
Нитин Агарвал
Разве вы не можете избежать взаимоблокировок в этом сценарии с помощью (Task) .ConfigureAwait (false) .GetAwaiter (). GetResult (); ?
Даниэль Лоренц
3
@DanielLorenz: См. Следующую цитату: «Использование ConfigureAwait (false) для предотвращения взаимоблокировок - опасная практика. Вам придется использовать ConfigureAwait (false) для каждого await в транзитивном закрытии всех методов, вызываемых кодом блокировки, включая все третьи - и сторонний код. Использование ConfigureAwait (false) для предотвращения взаимоблокировки - в лучшем случае просто взлом). ... лучшее решение - «Не блокировать асинхронный код». - blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Нитин Агарвал
4
Я не понимаю. Task.Wait и Task.Result разбиты по дизайну? Почему они не устаревают?
осэксперт
70

https://github.com/aspnet/Security/issues/59

"И последнее замечание: вам следует избегать использования Task.Resultи Task.Waitпо возможности, поскольку они всегда инкапсулируют внутреннее исключение в AggregateExceptionи заменяют сообщение общим (произошла одна или несколько ошибок), что затрудняет отладку. Даже если синхронная версия не должна не будет использоваться так часто, вам следует серьезно подумать об использовании Task.GetAwaiter().GetResult()вместо этого ".

Scyuo
источник
22
Источник, на который здесь делается ссылка, - это кто-то цитирующий кого-то без ссылки. Примите во внимание контекст: я вижу, как многие люди слепо используют GetAwaiter (). GetResult () везде после прочтения этого.
Джек Уклея,
2
Значит, мы не должны его использовать?
tofutim
13
Если две задачи завершатся исключением, в этом сценарии вы потеряете вторую Task.WhenAll(task1, task2).GetAwaiter().GetResult();.
Monsignor
Вот еще один пример: github.com/aspnet/AspNetCore/issues/13611
Георгий Чахидзе
38

Другое отличие заключается в том, что asyncфункция возвращает только Taskвместо Task<T>этого, вы не можете использовать

GetFooAsync(...).Result;

В то время как

GetFooAsync(...).GetAwaiter().GetResult();

еще работает.

Я знаю, что пример кода в вопросе предназначен для данного случая Task<T>, однако вопрос задается в целом.

Нури Тасдемир
источник
1
Это неправда. Проверьте мою скрипку , которая использует именно эту конструкцию: dotnetfiddle.net/B4ewH8
wojciech_rak
4
@wojciech_rak В коде, вы используете Resultс GetIntAsync()которой возвращается Task<int>не только Task. Предлагаю вам еще раз прочитать мой ответ.
Нури Тасдемир
1
Вы правы, сначала я понял, что вы отвечаете, что нельзя GetFooAsync(...).Result внутри возвращающей функции Task. Теперь это имеет смысл, поскольку в C # нет void Properties ( Task.Resultэто свойство), но вы, конечно, можете вызвать метод void.
wojciech_rak
28

Как уже упоминалось, если вы можете использовать await. Если вам нужно запускать код синхронно, как вы упомянули .GetAwaiter().GetResult(), .Resultили .Wait()существует риск возникновения взаимоблокировок, как многие сказали в комментариях / ответах. Поскольку большинству из нас нравятся одинарные лайнеры, вы можете использовать их для.Net 4.5<

Получение значения с помощью асинхронного метода:

var result = Task.Run(() => asyncGetValue()).Result;

Синхронный вызов асинхронного метода

Task.Run(() => asyncMethod()).Wait();

Проблемы с взаимоблокировкой не возникнут из-за использования Task.Run.

Источник:

https://stackoverflow.com/a/32429753/3850405

Огглас
источник
1

В случае сбоя задачи исключение создается повторно, когда код продолжения вызывает awaiter.GetResult (). Вместо вызова GetResult мы могли бы просто получить доступ к свойству Result задачи. Преимущество вызова GetResult заключается в том, что в случае сбоя задачи исключение выдается напрямую, без упаковки в AggregateException, что позволяет использовать более простые и чистые блоки catch.

Для неуниверсальных задач GetResult () возвращает недействительное значение. Его полезная функция состоит исключительно в том, чтобы повторно генерировать исключения.

источник: С # 7.0 в двух словах

Али Абдоллахи
источник