whenall - task httpresponsemessage c#
Cualquier diferencia entre "espera Task.Run(); return; "y" return Task.Run() "? (4)
¿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é?
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<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 es entre tus dos cosas.
El primer método ni siquiera compila.
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<T>
''?Tiene que ser
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
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 lasasync
internas deasync
/await
.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/''<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::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::''<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::TestAsync2
Los dos ejemplos son diferentes. Cuando un método se marca con la palabra clave async
, el compilador 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.
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 deasync 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<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 a DoTestAsync(OneTestAsync, -2)
, produce el siguiente resultado:
Press enter to continue Error: One or more errors occurred.await Task.Delay Error: 2nd
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: The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer. Parameter name: millisecondsDelayError: 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 pila.
Publiqué algunos más detalles sobre el comportamiento de manejo de excepciones de TPL here y here .
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 pila?
R : Si es realmente 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, RunSynchronously
cuenta que, bajo ciertas condiciones (como cuando está demasiado profundo en la pila), RunSynchronously
aún podría ejecutarse de forma asíncrona.