¿Existe alguna diferencia conceptual entre las siguientes dos piezas de código?
async Task TestAsync()
{
await Task.Run(() => DoSomeWork());
}
y
Task TestAsync()
{
return Task.Run(() => DoSomeWork());
}
¿El código generado también es diferente?
EDITAR: Para evitar confusiones con Task.Run, un caso similar:
async Task TestAsync()
{
await Task.Delay(1000);
}
y
Task TestAsync()
{
return Task.Delay(1000);
}
ACTUALIZACIÓN TARDÍA: además de la respuesta aceptada, también hay una diferencia en cómo LocalCallContextse maneja: CallContext.LogicalGetData se restaura incluso cuando no hay asincronía. ¿Por qué?
c#
async-await
avo
fuente
fuente

await/asyncen absoluto :)Respuestas:
Una diferencia importante está en la propagación de excepciones. Una excepción, arrojado dentro de un
async Taskmétodo, se almacena en el vueltoTaskobjeto y permanece latente hasta que la tarea se observa a través deawait task,task.Wait(),task.Resultotask.GetAwaiter().GetResult(). Se propaga de esta manera incluso si se lanza desde la parte sincrónica delasyncmétodo.Considere el siguiente código, donde
OneTestAsyncy seAnotherTestAsynccomportan 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<int, Task> 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
DoTestAsync(OneTestAsync, -2), produce la siguiente salida:Tenga en cuenta que tuve que presionar Enterpara verlo.
Ahora, si llamo
DoTestAsync(AnotherTestAsync, -2), el flujo de trabajo del código internoDoTestAsynces bastante diferente, al igual que la salida. Esta vez, no me pidieron que presione Enter:En ambos casos se
Task.Delay(-2)lanza al principio, mientras valida sus parámetros. Este podría ser un escenario inventado, pero en teoríaTask.Delay(1000)también puede producirse , por ejemplo, cuando falla la API del temporizador del sistema subyacente.En una nota al margen, la lógica de propagación de errores es aún diferente para los
async voidmétodos (a diferencia de losasync Taskmétodos). Una excepción generada dentro de unasync voidmétodo se volverá a lanzar inmediatamente en el contexto de sincronización del hilo actual (víaSynchronizationContext.Post), si el hilo actual tiene uno (SynchronizationContext.Current != null). De lo contrario, se volverá a lanzar víaThreadPool.QueueUserWorkItem). La persona que llama no tiene la oportunidad de manejar esta excepción en el mismo marco de pila.Publiqué algunos detalles más sobre el comportamiento de manejo de excepciones de TPL aquí y aquí .
P : ¿Es posible imitar el comportamiento de propagación de excepciones de los
asyncmétodos para métodos noTaskbasados en asíncronos , de modo que este último no se ejecute en el mismo marco de pila?R : Si realmente es necesario, entonces sí, hay un truco para eso:
// async async Task<int> MethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async Task<int> MethodAsync(int arg) { var task = new Task<int>(() => { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; }); task.RunSynchronously(TaskScheduler.Default); return task; }Sin embargo, tenga en cuenta que bajo ciertas condiciones (como cuando está demasiado profundo en la pila),
RunSynchronouslyaún podría ejecutarse de forma asincrónica.Otra diferencia notable es que la versión
async/awaites más propensa a bloquearse en un contexto de sincronización no predeterminado . Por ejemplo, lo siguiente se bloqueará 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 una versión no asíncrona y no se bloqueará:
Task TestAsync() { return Task.Delay(1000); }La naturaleza del bloqueo está bien explicada por Stephen Cleary en su blog .
fuente
return Task.Run()yawait Task.Run(); return, en lugar deawait Task.Run().ConfigureAwait(false); returnEstoy confundido por esta pregunta. Permítame intentar aclararlo respondiendo a su pregunta con otra pregunta. ¿Cuál es la diferencia entre?
Func<int> MakeFunction() { Func<int> f = ()=>1; return ()=>f(); }y
Func<int> MakeFunction() { return ()=>1; }?
Cualquiera que sea la diferencia entre mis dos cosas, la misma diferencia existe entre tus dos cosas.
fuente
Task.Delay(1000).ContinueWith(() = {}). En el segundo, es soloTask.Delay(1000). La diferencia es algo sutil, pero significativa.El primer método ni siquiera se compila.
Tiene que ser
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }Existe una gran diferencia conceptual entre estos dos. El primero es asincrónico, el segundo no lo es. Lea Async Performance: Understanding the Costs of Async y Await para obtener más información sobre los aspectos internos de
async/await.Generan código diferente.
.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/'<TestAsync>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/'<TestAsync>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/'<TestAsync>d__1'::'<>t__builder' IL_0014: ldloca.s 0 IL_0016: ldc.i4.m1 IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state' IL_001c: ldloca.s 0 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>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::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&) IL_002d: ldloca.s 0 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>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::TestAsyncy
.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::'<TestAsync2>b__4'() IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int) IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>) IL_0012: stloc.0 IL_0013: br.s IL_0015 IL_0015: ldloc.0 IL_0016: ret } // end of method Program::TestAsync2fuente
Los dos ejemplos no difieren. Cuando un método está marcado con la
asyncpalabra clave, el compilador genera una máquina de estado detrás de escena. Esto es lo que se encarga de reanudar las continuaciones una vez que se ha esperado una esperada.Por el contrario, cuando un método no está marcado con
async, se pierde la capacidad deawaitesperar. (Es decir, dentro del método en sí; la persona que llama aún puede esperar el método). Sin embargo, al evitar laasyncpalabra clave, ya no está generando la máquina de estado, que puede agregar un poco de sobrecarga (elevando locales a los campos de la máquina de estado, objetos adicionales al GC).En ejemplos como este, si puede evitar
async-awaity devolver un awaitable directamente, debe hacerlo para mejorar la eficiencia del método.Vea esta pregunta y esta respuesta que son muy similares a su pregunta y esta respuesta.
fuente