example - public static async void c#
Captura una excepción lanzada por un método asíncrono (6)
Usando el CTP asíncrono de Microsoft para .NET, ¿es posible detectar una excepción lanzada por un método asíncrono en el método de llamada?
public async void Foo()
{
var x = await DoSomethingAsync();
/* Handle the result, but sometimes an exception might be thrown.
For example, DoSomethingAsync gets data from the network
and the data is invalid... a ProtocolException might be thrown. */
}
public void DoFoo()
{
try
{
Foo();
}
catch (ProtocolException ex)
{
/* The exception will never be caught.
Instead when in debug mode, VS2010 will warn and continue.
The deployed the app will simply crash. */
}
}
Básicamente, quiero que la excepción del código asíncrono se convierta en mi código de llamada, si es que es posible.
Es un poco raro de leer, pero sí, la excepción aumentará hasta el código de llamada, pero solo si await
o Wait()
la llamada a Foo
.
public async void DoFoo()
{
try
{
await Foo();
}
catch (ProtocolException ex)
{
// The exception will be caught because you''ve awaited
// the call in an async method.
}
}
//or//
public void DoFoo()
{
try
{
Foo().Wait();
}
catch (ProtocolException ex)
{
/* The exception will be caught because you''ve
waited for the completion of the call. */
}
}
Los métodos de vacío asíncronos tienen diferentes semánticas de manejo de errores. Cuando se expulsa una excepción de un método de tarea asíncrona o de tarea asíncrona, esa excepción se captura y se coloca en el objeto de tarea. Con los métodos de vacío asíncrono, no hay ningún objeto de Tarea, por lo que cualquier excepción expulsada de un método de vacío asíncrono se generará directamente en el SynchronizationContext que estaba activo cuando se inició el método de vacío asíncrono. - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
Tenga en cuenta que el uso de Esperar () puede hacer que su aplicación se bloquee, si .Net decide ejecutar su método de forma síncrona.
Esta explicación http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions es bastante buena: analiza los pasos que toma el compilador para lograr esta magia.
Este blog explica su problema de forma asincrónica con las mejores prácticas .
La esencia de esto es que no debe usar void como retorno para un método asíncrono, a menos que sea un controlador asíncrono de eventos, esto es una mala práctica porque no permite detectar excepciones ;-).
La mejor práctica sería cambiar el tipo de retorno a Tarea. Además, intente codificar async todo el camino, haga que cada método async se llame y se le llame desde métodos async. Excepto por un método principal en una consola, que no puede ser asíncrono (antes de C # 7.1).
Se encontrará con interbloqueos con las aplicaciones GUI y ASP.NET si ignora esta práctica recomendada. El interbloqueo se produce porque estas aplicaciones se ejecutan en un contexto que permite solo un subproceso y no lo entregará al subproceso asíncrono. Esto significa que la GUI espera sincrónicamente una devolución, mientras que el método asíncrono espera el contexto: punto muerto.
Este comportamiento no ocurrirá en una aplicación de consola, porque se ejecuta en contexto con un grupo de subprocesos. El método asíncrono volverá a otro hilo que se programará. Esta es la razón por la que una aplicación de consola de prueba funcionará, pero las mismas llamadas se interbloquearán en otras aplicaciones ...
La excepción puede ser atrapada en la función asíncrona.
public async void Foo()
{
try
{
var x = await DoSomethingAsync();
/* Handle the result, but sometimes an exception might be thrown
For example, DoSomethingAsync get''s data from the network
and the data is invalid... a ProtocolException might be thrown */
}
catch (ProtocolException ex)
{
/* The exception will be caught here */
}
}
public void DoFoo()
{
Foo();
}
La razón por la que no se detecta la excepción es porque el método Foo () tiene un tipo de retorno nulo y, por lo tanto, cuando se llama a la espera, simplemente se devuelve. Como DoFoo () no está esperando la finalización de Foo, no se puede usar el controlador de excepciones.
Esto abre una solución más simple si puede cambiar las firmas del método; modifique Foo()
para que devuelva el tipo de Task
y luego DoFoo()
pueda await Foo()
, como en este código:
public async Task Foo() {
var x = await DoSomethingThatThrows();
}
public async void DoFoo() {
try {
await Foo();
} catch (ProtocolException ex) {
// This will catch exceptions from DoSomethingThatThrows
}
}
También es importante tener en cuenta que perderá el seguimiento cronológico de la pila de la excepción si tiene un tipo de retorno nulo en un método asíncrono. Recomendaría devolver la tarea de la siguiente manera. Va a hacer la depuración mucho más fácil.
public async Task DoFoo()
{
try
{
return await Foo();
}
catch (ProtocolException ex)
{
/* Exception with chronological stack trace */
}
}
Tu código no hace lo que piensas que hace. Los métodos asíncronos regresan inmediatamente después de que el método comience a esperar el resultado asíncrono. Es perspicaz usar el rastreo para investigar cómo se comporta realmente el código.
El siguiente código hace lo siguiente:
- Crear 4 tareas
- Cada tarea incrementará de forma asíncrona un número y devolverá el número incrementado
- Cuando llega el resultado asíncrono se remonta.
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 Ejecutar se completa en el subproceso 2820 mientras que solo un subproceso secundario ha finalizado (2756). Si coloca un método try / catch alrededor de su método de espera, puede "capturar" la excepción de la forma habitual, aunque su código se ejecute en otro hilo cuando la tarea de cálculo haya finalizado y se haya ejecutado su contiuation.
El método de cálculo rastrea la excepción lanzada automáticamente porque usé el ApiChange.Api.dll de la herramienta ApiChange . Rastreo y Reflector ayudan mucho a entender lo que está pasando. Para deshacerse de los subprocesos, puede crear sus propias versiones de GetAwaiter BeginAwait y EndAwait y envolver no una tarea sino, por ejemplo, un Lazy y rastrear dentro de sus propios métodos de extensión. Entonces entenderás mejor el compilador y lo que hace el TPL.
Ahora ve que no hay forma de volver a intentar / recuperar su excepción, ya que no queda ningún marco de pila para que se propague ninguna excepción. Su código podría estar haciendo algo totalmente diferente después de haber iniciado las operaciones asíncronas. Podría llamar Thread.Sleep o incluso terminar. Mientras haya un hilo de primer plano, su aplicación continuará felizmente ejecutando tareas asíncronas.
Puede manejar la excepción dentro del método asíncrono después de que su operación asíncrona finalice y vuelva a llamar al hilo de la interfaz de usuario. La forma recomendada de hacer esto es con TaskScheduler.FromSynchronizationContext . Eso solo funciona si tienes un subproceso de UI y no está muy ocupado con otras cosas.