2012-01-10 4 views
16

Por qué excepciones lanzadas dentro de una tarea son una excepción en silencio y nunca se sabe si un determinado Se inició una excepciónexcepción de tareas y el silencio

try 
{ 

Task task = new Task(
() => { 
      throw null; 
     } 
     ); 
    task.Start(); 
} 
catch 
{ 
    Console.WriteLine("Exception"); 
} 

el programa se ejecute con éxito en un silencio completo! donde el comportamiento de los hilos es diferente

try 
{ 

Thread thread = new Thread(
() => { 
      throw null; 
     } 
     ); 
    thread .Start(); 
} 
catch 
{ 
    Console.WriteLine("Exception"); 
} 

una excepción de puntero nulo serán arrojados en este caso. ¿Cuál es la diferencia?

Respuesta

15

El comportamiento de ese escenario depende del marco que tenga; en 4.0, realmente debe tener cuidado: si no maneja TaskScheduler.UnobservedTaskException, se producirá el error posterior cuando se recopile/finalice, y matará su proceso.

TaskScheduler.UnobservedTaskException += (sender, args) => 
{ 
    Trace.WriteLine(args.Exception.Message); // somebody forgot to check! 
    args.SetObserved(); 
}; 

Esto cambia en 4.5, IIRC.

Para comprobar el resultado de un Task que fuerzas fallan, se podría registrar una continuación - es decir, llaman ContinueWith y comprobar excepción del resultado. Alternativamente, acceder al .Result de la tarea (que también haría un implícito Wait()) volverá a aparecer la excepción que sucedió. Es bueno observar el resultado de una tarea, ya que eso borra la bandera de finalización, lo que significa que se puede recolectar a menor costo.

+0

Cambió el número de versión de 5.0 a 4.5. Espero que no te importe. (Será C# 5, pero .NET 4.5.) –

+0

@Jon en absoluto; de hecho, gracias por corregirme. –

5

No, las tareas no son subprocesos. Las tareas representan una abstracción de alto nivel: son una unidad de trabajo intrínsecamente paralela. Los hilos ejecutan unidades de trabajo.

En su primer ejemplo, crea una unidad de trabajo y luego le dice que se ejecute solo (cómo lo hace es un detalle de implementación de Tarea). Mientras que en su segundo ejemplo, usted programa explícitamente una unidad de trabajo (se vería de una manera diferente a la implementación de la Tarea).

+0

bien, pero ¿qué pasa con el manejo de excepciones? –

+0

El OP está más interesado en por qué 'Tarea' no arrojará mientras que' Subproceso' lo hará. – Oded

+2

@sleimanjneidi Las tareas almacenarán la excepción para cuando se interrogue la tarea. En un hilo no hay manejo de excepciones inherentes, se propagará por la pila hasta que se capture la excepción. –

1

Creo que no recibirá una excepción en el caso de Task porque no está esperando una excepción en el hilo principal. Tu solo estas continuando. Ponga task.Wait() y obtendrá la excepción en el hilo principal.

+1

Muy cierto, pero para ser justos, en la mayoría de los casos es * esperado * que no llamemos inmediatamente a 'Esperar'. –

4

Lo siguiente asume .NET 4.0 y el uso del TaskScheduler por defecto.

Ante todo aviso de que las excepciones son criados dentro los delegados que pasas, por lo que se crían en un hilo diferente, no en el que (lógicamente) está haciendo su catch. De alguna forma, la excepción debe propagarse desde el hilo que ejecuta su delegado/código lambda al que inició el hilo/tarea.

Tenga en cuenta que para Task, creo que la biblioteca también puede optar por no ejecutarlo en su propio hilo, sino más bien en el subproceso de llamada (pero no estoy seguro de si era cierto que sólo para Parallel.ForEach, etc., y no para un objeto "desnudo" Task).

Para que dos suceda, dos cosas deben cumplirse:

  1. hilo El llamado es todavía activo. De lo contrario, no queda nada que realmente pueda realizar la captura.
  2. La excepción que se produjo en el subproceso/tarea debe de alguna manera conservarse y volverse a subir para que pueda atraparla.

Dicho esto, ambos ejemplos no esperan a que el subproceso/tarea finalice. En el primer ejemplo, falta un task.Wait() (o similar) y en el segundo, un thread.Join(). Dependiendo del comportamiento de temporización de los códigos de prueba, esto puede significar que nunca podrá observar la excepción del subproceso/tarea (elemento 1 anterior).

Incluso si se agrega una llamada a esto es lo que sucede para mí (de nuevo .NET 4.0):

  • Tarea ejemplo: la llamada a task.Wait() realidad vuelve a subir la excepción dada originalmente no controlada en la tarea de delegar (esto es lo que el TPL hará por ti internamente), lo envuelve dentro de un System.AggregateException, que podrías/verías si usas algo más preciso que un "catch-all" plano.

  • Tema ejemplo: la excepción planteada por el delegado sigue siendo controlada y sus salidas de aplicación (a menos que haga algo para hacer frente a unhandled exceptions differently)

En otras palabras, me tendría los ejemplos de la siguiente manera:

// Thread example 
var thread = new Thread(() => { throw null; }); 
thread.Start(); 
thread.Join(); 
// Should never reach here, depending on timing sometimes not even 
// until the Join() call. The process terminates as soon as the other 
// thread runs the "throw null" code - which can logically happen somewhere 
// after the "start" of the "Start()" call. 

// Task example 
try 
{ 

    var task = new Task(() => { throw null; }); 
    task.Start(); 
    task.Wait(); 
} 
catch (AggregateException ex) 
{ 
    Console.WriteLine("Exception: " + ex); 
} 
Cuestiones relacionadas