Su código no hace lo que podría pensar que hace. Los métodos asíncronos regresan inmediatamente después de que el método comienza a esperar el resultado asíncrono. Es útil utilizar el rastreo para investigar cómo se comporta realmente el código.
El siguiente código hace lo siguiente:
- Crea 4 tareas
- Cada tarea incrementará asíncronamente un número y devolverá el número incrementado
- Cuando el resultado asíncrono ha llegado, se rastrea.
static TypeHashes _type = new TypeHashes(typeof(Program));
private void Run()
{
TracerConfig.Reset("debugoutput");
using (Tracer t = new Tracer(_type, "Run"))
{
for (int i = 0; i < 4; i++)
{
DoSomeThingAsync(i);
}
}
Application.Run(); // Start window message pump to prevent termination
}
private async void DoSomeThingAsync(int i)
{
using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
{
t.Info("Hi in DoSomething {0}",i);
try
{
int result = await Calculate(i);
t.Info("Got async result: {0}", result);
}
catch (ArgumentException ex)
{
t.Error("Got argument exception: {0}", ex);
}
}
}
Task<int> Calculate(int i)
{
var t = new Task<int>(() =>
{
using (Tracer t2 = new Tracer(_type, "Calculate"))
{
if( i % 2 == 0 )
throw new ArgumentException(String.Format("Even argument {0}", i));
return i++;
}
});
t.Start();
return t;
}
Cuando observas las huellas
22:25:12.649 02172/02820 { AsyncTest.Program.Run
22:25:12.656 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.657 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0
22:25:12.658 02172/05220 { AsyncTest.Program.Calculate
22:25:12.659 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.659 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1
22:25:12.660 02172/02756 { AsyncTest.Program.Calculate
22:25:12.662 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.662 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2
22:25:12.662 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.662 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3
22:25:12.664 02172/02756 } AsyncTest.Program.Calculate Duration 4ms
22:25:12.666 02172/02820 } AsyncTest.Program.Run Duration 17ms ---- Run has completed. The async methods are now scheduled on different threads.
22:25:12.667 02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1
22:25:12.667 02172/02756 } AsyncTest.Program.DoSomeThingAsync Duration 8ms
22:25:12.667 02172/02756 { AsyncTest.Program.Calculate
22:25:12.665 02172/05220 Exception AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
22:25:12.668 02172/02756 Exception AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
22:25:12.724 02172/05220 } AsyncTest.Program.Calculate Duration 66ms
22:25:12.724 02172/02756 } AsyncTest.Program.Calculate Duration 57ms
22:25:12.725 02172/05220 Error AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0
Server stack trace:
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
Exception rethrown at [0]:
at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()
at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()
at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106
22:25:12.725 02172/02756 Error AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2
Server stack trace:
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
Exception rethrown at [0]:
at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()
at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()
at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0
22:25:12.726 02172/05220 } AsyncTest.Program.DoSomeThingAsync Duration 70ms
22:25:12.726 02172/02756 } AsyncTest.Program.DoSomeThingAsync Duration 64ms
22:25:12.726 02172/05220 { AsyncTest.Program.Calculate
22:25:12.726 02172/05220 } AsyncTest.Program.Calculate Duration 0ms
22:25:12.726 02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3
22:25:12.726 02172/05220 } AsyncTest.Program.DoSomeThingAsync Duration 64ms
Notará que el método Run se completa en el hilo 2820 mientras que solo un hilo hijo ha terminado (2756). Si pone un método try / catch alrededor de su método de espera, puede "capturar" la excepción de la manera habitual, aunque su código se ejecuta en otro hilo cuando la tarea de cálculo ha finalizado y se ejecuta su continuación.
El método de cálculo rastrea la excepción lanzada automáticamente porque utilicé ApiChange.Api.dll de la herramienta ApiChange . Tracing and Reflector ayuda mucho a comprender lo que está sucediendo. Para deshacerse de los subprocesos, puede crear sus propias versiones de GetAwaiter BeginAwait y EndAwait y ajustar no una tarea sino, por ejemplo, un Lazy y rastrear dentro de sus propios métodos de extensión. Entonces comprenderá mucho mejor lo que hace el compilador y lo que hace el TPL.
Ahora verá que no hay forma de volver a intentar / atrapar su excepción ya que no queda ningún marco de pila para que se propague cualquier excepción. Su código podría estar haciendo algo totalmente diferente después de haber iniciado las operaciones asíncronas. Puede llamar a Thread.Sleep o incluso terminar. Mientras quede un subproceso en primer plano, su aplicación seguirá felizmente ejecutando tareas asincrónicas.
Puede manejar la excepción dentro del método asincrónico después de que su operación asincrónica haya finalizado y volver a llamar al hilo de la interfaz de usuario. La forma recomendada de hacerlo es con TaskScheduler.FromSynchronizationContext . Eso solo funciona si tiene un subproceso de interfaz de usuario y no está muy ocupado con otras cosas.