Cualquier diferencia entre “espera Task.Run (); return; “y” return Task.Run () “?

¿Hay alguna diferencia conceptual entre los siguientes dos códigos:

async Task TestAsync() { await Task.Run(() => DoSomeWork()); } 

y

 Task TestAsync() { return Task.Run(() => DoSomeWork()); } 

¿El código generado difiere?

EDITAR: para evitar confusiones con Task.Run , un caso similar:

 async Task TestAsync() { await Task.Delay(1000); } 

y

 Task TestAsync() { return Task.Delay(1000); } 

ÚLTIMA ACTUALIZACIÓN: Además de la respuesta aceptada, también existe una diferencia en cómo se maneja LocalCallContext : CallContext.LogicalGetData se restaura incluso cuando no hay asincronía. ¿Por qué?

Actualizado , además de las diferencias en el comportamiento de propagación de excepciones que se explica a continuación, hay otra diferencia algo sutil: la versión async / await es más propensa a dead-locking en un contexto de sincronización no predeterminado. Por ejemplo, lo siguiente será dead-lock en una aplicación WinForms o WPF:

 static async Task TestAsync() { await Task.Delay(1000); } void Form_Load(object sender, EventArgs e) { TestAsync().Wait(); // dead-lock here } 

Cámbielo a la versión no asíncrona y no se bloqueará a cero:

 Task TestAsync() { return Task.Delay(1000); } 

La naturaleza de la cerradura está bien explicada por Stephen Cleary en su blog .


Otra gran diferencia está en la propagación de excepciones. Una excepción, arrojada dentro de un método de async Task , se almacena en el objeto Task devuelto y permanece inactiva hasta que la tarea se observe mediante la await task , task.Wait() , task.Result o task.GetAwaiter().GetResult() . Se propaga de esta manera incluso si se lanza desde la parte síncrona del método async .

Considere el siguiente código, donde OneTestAsync y AnotherTestAsync comportan de manera bastante diferente:

 static async Task OneTestAsync(int n) { await Task.Delay(n); } static Task AnotherTestAsync(int n) { return Task.Delay(n); } // call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest static void DoTestAsync(Func whatTest, int n) { Task task = null; try { // start the task task = whatTest(n); // do some other stuff, // while the task is pending Console.Write("Press enter to continue"); Console.ReadLine(); task.Wait(); } catch (Exception ex) { Console.Write("Error: " + ex.Message); } } 

Si llamo a DoTestAsync(OneTestAsync, -2) , produce el siguiente resultado:

 Presione enter para continuar
 Error: se produjeron uno o más errores. Aguarde la tarea.Delay
 Error: 2do

Tenga en cuenta que tuve que presionar Enter para verlo.

Ahora, si llamo a DoTestAsync(AnotherTestAsync, -2) , el flujo de trabajo del código dentro de DoTestAsync es bastante diferente, y también lo es el resultado. Esta vez, no me pidieron que presionara Enter :

 Error: el valor debe ser -1 (que significa un tiempo de espera infinito), 0 o un entero positivo.
 Nombre del parámetro: milisegundosDelayError: 1st

En ambos casos, Task.Delay(-2) tira al principio, mientras valida sus parámetros. Este podría ser un escenario inventado, pero en teoría Task.Delay(1000) puede lanzar, por ejemplo, cuando falla la API subyacente del temporizador del sistema.

En una nota lateral, la lógica de propagación de errores aún es diferente para los métodos de async void (a diferencia de los métodos de async Task ). Una excepción generada dentro de un método de async void se relanzará inmediatamente en el contexto de sincronización del hilo actual (a través de SynchronizationContext.Post ), si el hilo actual tiene uno ( SynchronizationContext.Current != null) . De lo contrario, se volverá a lanzar a través de ThreadPool.QueueUserWorkItem ). La persona que llama no tiene la oportunidad de manejar esta excepción en el mismo marco de stack.

Publiqué algunos más detalles sobre el comportamiento de manejo de excepciones de TPL aquí y aquí .


P : ¿Es posible imitar el comportamiento de propagación de excepción de los métodos async para métodos basados ​​en Task no asincrónicas, de modo que este último no arroje el mismo marco de stack?

R : Si es realmente necesario, entonces sí, hay un truco para eso:

 // async async Task MethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async Task MethodAsync(int arg) { var task = new Task(() => { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; }); task.RunSynchronously(TaskScheduler.Default); return task; } 

Sin embargo, RunSynchronously cuenta que, bajo ciertas condiciones (como cuando está demasiado profundo en la stack), RunSynchronously aún podría ejecutarse de forma asíncrona.

Cuál es la diferencia entre

 async Task TestAsync() { await Task.Delay(1000); } 

y

 Task TestAsync() { return Task.Delay(1000); } 

?

Estoy confundido por esta pregunta. Permítanme intentar aclarar respondiendo a su pregunta con otra pregunta. ¿Cuál es la diferencia entre?

 Func MakeFunction() { Func f = ()=>1; return ()=>f(); } 

y

 Func MakeFunction() { return ()=>1; } 

?

Cualquiera que sea la diferencia entre mis dos cosas, la misma diferencia es entre tus dos cosas.

  1. El primer método ni siquiera comstack.

    Dado que ‘ Program.TestAsync() ‘ es un método asíncrono que devuelve ‘ Task ‘, una palabra clave de retorno no debe ser seguida por una expresión de objeto. ¿Pretendes devolver ‘ Task ‘?

    Tiene que ser

     async Task TestAsync() { await Task.Run(() => DoSomeWork()); } 
  2. Hay una gran diferencia conceptual entre estos dos. El primero es asincrónico, el segundo no. Lea el rendimiento async : Comprenda los costos de Async y espere obtener un poco más sobre las async internas de async / await .

  3. Generan códigos diferentes.

     .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74 2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73 79 6e 63 3e 64 5f 5f 31 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x216c // Code size 62 (0x3e) .maxstack 2 .locals init ( [0] valuetype SOTestProject.Program/'d__1', [1] class [mscorlib]System.Threading.Tasks.Task, [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ) IL_0000: ldloca.s 0 IL_0002: ldarg.0 IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'d__1'::'<>4__this' IL_0008: ldloca.s 0 IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create() IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'d__1'::'<>t__builder' IL_0014: ldloca.s 0 IL_0016: ldc.i4.m1 IL_0017: stfld int32 SOTestProject.Program/'d__1'::'<>1__state' IL_001c: ldloca.s 0 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'d__1'::'<>t__builder' IL_0023: stloc.2 IL_0024: ldloca.s 2 IL_0026: ldloca.s 0 IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Startd__1'>(!!0&) IL_002d: ldloca.s 0 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'d__1'::'<>t__builder' IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task() IL_0039: stloc.1 IL_003a: br.s IL_003c IL_003c: ldloc.1 IL_003d: ret } // end of method Program::TestAsync 

    y

     .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed { // Method begins at RVA 0x21d8 // Code size 23 (0x17) .maxstack 2 .locals init ( [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'b__4'() IL_0008: newobj instance void class [mscorlib]System.Func`1::.ctor(object, native int) IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1) IL_0012: stloc.0 IL_0013: br.s IL_0015 IL_0015: ldloc.0 IL_0016: ret } // end of method Program::TestAsync2 

Los dos ejemplos son diferentes. Cuando un método se marca con la palabra clave async , el comstackdor genera una máquina de estados detrás de las escenas. Esto es lo que es responsable de reanudar las continuación una vez que se ha esperado una espera.

Por el contrario, cuando un método no está marcado con async , está perdiendo la capacidad de await quedan. (Es decir, dentro del método en sí mismo, el método aún puede ser esperado por su interlocutor). Sin embargo, al evitar la palabra clave async , ya no genera la máquina de estado, lo que puede agregar un poco de sobrecarga (levantando locales para campos de la máquina de estado, objetos adicionales al GC).

En ejemplos como este, si puede evitar async-await await y devolver un awaitable directamente, se debe hacer para mejorar la eficiencia del método.

Vea esta pregunta y esta respuesta que son muy similares a su pregunta y esta respuesta.