¿Task.Result es lo mismo que .GetAwaiter.GetResult ()?

328

Recientemente estuve leyendo un código que usa muchos métodos asíncronos, pero que a veces necesita ejecutarlos sincrónicamente. El código hace:

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

¿Es esto lo mismo que

Foo foo = GetFooAsync(...).Result;
Jay Bazuzi
fuente
8
De los documentos de GetResult: "Este tipo y sus miembros están destinados a ser utilizados por el compilador". Otra persona no debería estar usándolo.
gastador
32
Esto se llama "sincronización sobre asíncrono" y, a menos que sepa cómo se implementa la tarea, puede ser una muy mala idea. Puede bloquearse instantáneamente en muchos casos (un async/ awaitmétodo en MVC, por ejemplo)
Marc Gravell
10
No bloquee el código
asíncrono
14
En el mundo real, tenemos constructores, tenemos interfaces "sin esperar" que necesitamos implementar, y nos dan métodos asíncronos en todas partes. Me complacería usar algo que simplemente funcione sin tener que preguntarme por qué es "peligroso", "no se debe usar" o "evitar a toda costa". Cada vez que tengo que meterme con async resulta ser un dolor de cabeza.
Larry

Respuestas:

173

Más o menos. Sin embargo, una pequeña diferencia: si Taskfalla, GetResult()solo arrojará la excepción causada directamente, mientras Task.Resultque arrojará un AggregateException. Sin embargo, ¿cuál es el punto de usar cualquiera de esos cuando es async? La opción 100x mejor es usar await.

Además, no estás destinado a usar GetResult(). Está destinado a ser solo para uso del compilador, no para ti. Pero si no quieres lo molesto AggregateException, úsalo .

Es notalie.
fuente
27
@JayBazuzi No si su marco de pruebas unitarias admite pruebas unitarias asíncronas, lo que creo que las versiones más recientes de la mayoría de los marcos sí.
svick
15
@JayBazuzi: MSTest, xUnit y NUnit son todas async Taskunidades de prueba de soporte , y lo han hecho por algún tiempo.
Stephen Cleary
18
empujando hacia atrás en el 100x: es 1000x peor usar el sistema de espera si está adaptando el código anterior y el uso de esperar requiere una reescritura.
atrapado el
13
@AlexZhukovskiy: No estoy de acuerdo .
Stephen Cleary
15
The 100x better option is to use await.Odio las declaraciones como esta, si pudiera abofetearlo awaitlo haría. Pero, cuando estoy tratando de obtener el código asíncrono de trabajo contra el código asíncrono no como lo que sucede con frecuencia para mí una gran cantidad de Xamarin, me acaban de tener que usar cosas como ContinueWithuna gran cantidad con el fin de hacer que no punto muerto la interfaz de usuario. Editar: Sé que esto es viejo, pero eso no alivia mi frustración al encontrar respuestas que indican esto sin alternativas para situaciones en las que no se puede usar await.
Thomas F.
147

Task.GetAwaiter().GetResult()se prefiere sobre Task.Waity Task.Resultporque propaga excepciones en lugar de envolverlas en un AggregateException. Sin embargo, los tres métodos causan la posibilidad de problemas de inmovilización y de grupo de subprocesos. Todos deben ser evitados a favor de async/await.

La cita a continuación explica por qué Task.Waity Task.Resultno simplemente contiene el comportamiento de propagación de excepción de Task.GetAwaiter().GetResult()(debido a una "barra de compatibilidad muy alta").

Como mencioné anteriormente, tenemos una barra de compatibilidad muy alta y, por lo tanto, hemos evitado los cambios importantes. Como tal, Task.Waitconserva su comportamiento original de siempre envolver. Sin embargo, es posible que se encuentre en algunas situaciones avanzadas en las que desee un comportamiento similar al bloqueo sincrónico empleado Task.Wait, pero donde desee que la excepción original se propague sin envolver en lugar de encerrarse en un AggregateException. Para lograr eso, puede apuntar directamente al camarero de la Tarea. Cuando escribe " await task;", el compilador lo traduce al uso del Task.GetAwaiter()método, que devuelve una instancia que tiene un GetResult()método. Cuando se usa en una Tarea defectuosa, GetResult()propagará la excepción original (así es como " await task;" obtiene su comportamiento). Por lo tanto, puede usar "task.GetAwaiter().GetResult()"Si desea invocar directamente esta lógica de propagación.

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

" GetResult" En realidad significa "verificar la tarea en busca de errores"

En general, hago todo lo posible para evitar el bloqueo sincrónico en una tarea asincrónica. Sin embargo, hay un puñado de situaciones en las que sí viole esa directriz. En esas raras condiciones, mi método preferido es GetAwaiter().GetResult()porque conserva las excepciones de la tarea en lugar de envolverlas en un AggregateException.

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

Nitin Agarwal
fuente
3
Entonces, básicamente Task.GetAwaiter().GetResult()es equivalente a await task. Supongo que la primera opción se usa cuando el método no se puede marcar con async(constructor, por ejemplo). ¿Es eso correcto? Si es así, entonces choca con la respuesta principal @ It'sNotALie
OlegI
55
@OlegI: Task.GetAwaiter().GetResult()es más equivalente a Task.Waity Task.Result(ya que los tres se bloquearán sincrónicamente y tienen el potencial de puntos muertos), pero Task.GetAwaiter().GetResult()tiene el comportamiento de propagación de excepción de la tarea de espera.
Nitin Agarwal
¿No puede evitar puntos muertos en este escenario con (Task) .ConfigureAwait (false) .GetAwaiter (). GetResult (); ?
Daniel Lorenz
3
@DanielLorenz: Vea la siguiente cita: "Usar ConfigureAwait (falso) para evitar puntos muertos es una práctica peligrosa. Tendría que usar ConfigureAwait (falso) para cada espera en el cierre transitivo de todos los métodos llamados por el código de bloqueo, incluido el tercero - y código de segunda parte. Usar ConfigureAwait (falso) para evitar un punto muerto es, en el mejor de los casos, solo un truco) ... la mejor solución es "No bloquear el código asíncrono". - blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Nitin Agarwal
44
No lo entiendo Task.Wait y Task.Result están rotos por diseño? ¿Por qué no se vuelven obsoletos?
osexpert
69

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

"Una última observación: debe evitar el uso Task.Resulty en la Task.Waitmedida de lo posible, ya que siempre encapsulan la excepción interna en AggregateExceptiony reemplazan el mensaje por uno genérico (se produjeron uno o más errores), lo que dificulta la depuración. Incluso si la versión síncrona debería Si se usa con tanta frecuencia, debería considerar usarlo en su Task.GetAwaiter().GetResult()lugar ".

scyuo
fuente
20
La fuente a la que se hace referencia aquí es alguien citando a otra persona, sin una referencia. Considere el contexto: puedo ver a muchas personas ciegamente usando GetAwaiter (). GetResult () en todas partes después de leer esto.
Jack Ukleja
2
¿Entonces no deberíamos usarlo?
tofutim
11
Si dos tareas terminan con una excepción, perderá la segunda en este escenario Task.WhenAll(task1, task2).GetAwaiter().GetResult();.
Monseñor
Aquí hay otro ejemplo: github.com/aspnet/AspNetCore/issues/13611
George Chakhidze
33

Otra diferencia es cuando la asyncfunción regresa en Tasklugar de Task<T>no poder usar

GetFooAsync(...).Result;

Mientras

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

todavía funciona.

Sé que el código de ejemplo en la pregunta es para el caso Task<T>, sin embargo, la pregunta se hace generalmente.

Nuri Tasdemir
fuente
1
Esto no es verdad. Mira mi violín que usa exactamente esta construcción: dotnetfiddle.net/B4ewH8
wojciech_rak
3
@wojciech_rak En su código, está utilizando Resultcon el GetIntAsync()que devuelve Task<int>no solo Task. Le sugiero que lea mi respuesta nuevamente.
Nuri Tasdemir el
1
Tienes razón, al principio entendí que respondiste que no puedes GetFooAsync(...).Result dentro de una función que regresa Task. Esto ahora tiene sentido, ya que no hay propiedades nulas en C # ( Task.Resultes una propiedad), pero por supuesto puede llamar a un método nulo.
wojciech_rak
22

Como ya se mencionó si puede usar await. Si necesita ejecutar el código sincrónicamente como lo menciona .GetAwaiter().GetResult(), .Resulto si .Wait()existe un riesgo de puntos muertos, como muchos han dicho en los comentarios / respuestas. Como a la mayoría de nosotros nos gustan las líneas, puede usarlas para.Net 4.5<

Adquisición de un valor a través de un método asíncrono:

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

Llamar sincrónicamente a un método asíncrono

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

No se producirán problemas de punto muerto debido al uso de Task.Run.

Fuente:

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

Ogglas
fuente
1

Si falla una tarea, la excepción se vuelve a generar cuando el código de continuación llama a waititer.GetResult (). En lugar de llamar a GetResult, simplemente podríamos acceder a la propiedad Result de la tarea. El beneficio de llamar a GetResult es que si la tarea falla, la excepción se genera directamente sin estar envuelta en AggregateException, lo que permite bloques de captura más simples y limpios.

Para tareas no genéricas, GetResult () tiene un valor de retorno nulo. Su función útil es únicamente volver a lanzar excepciones.

fuente: c # 7.0 en una cáscara de nuez

Ali Abdollahi
fuente