2011-12-30 8 views
23

Tengo un método que crea algunas Tareas, y luego las espera con WaitAll antes de volver. El problema es que si esas tareas se cancelaron, WaitAll arroja un AggregateException que contiene muchos TaskCanceledException s.¿Cómo puedo esperar en las tareas sin lanzar TaskCanceledExceptions?

Eso significa que va a lanzar excepciones WaitAll en dos circunstancias diferentes:

  • excepciones que indican un error genuino. Esto significa que había una condición que no sabíamos cómo manejar; necesitan propagarse como excepciones no controladas, hasta que terminen el proceso.
  • Excepciones que indican que el usuario hizo clic en un botón Cancelar. Esto significa que la tarea fue cancelada y limpiada, y el programa debería continuar funcionando normalmente.

Este último encaja perfectamente en la definición de un vexing exception: se trata de una excepción lanzada en una circunstancia completamente no excepcional, así que tengo que cogerlo con el fin de reanudar el flujo normal de control. Afortunadamente es fácil de atrapar, ¿verdad? Solo agregue catch (AggregateException) y - oh espera, ese es el mismo tipo que se lanza cuando hay un error fatal.

Necesito esperar a que las tareas terminen de ejecutarse antes de volver (necesito saber que ya no usan sus conexiones de base de datos, manejadores de archivos o cualquier otra cosa), así que necesito el WaitAll o algo similar. Y si alguna de las tareas falla, quiero que esas excepciones se propaguen como excepciones no controladas. Simplemente no quiero excepciones para cancelar.

¿Cómo puedo evitar que WaitAll arroje excepciones para las tareas canceladas?

+0

uso de la sobrecarga que toma un CancellationToken. –

+0

@HansPassant, los documentos para esa sobrecarga en realidad no dicen para qué usa el token. ¿Ignorará TaskCanceledExceptions asociado con ese token, o simplemente regresará temprano si se cancela el token? Necesito que no regrese hasta que todas las tareas hayan dejado de ejecutarse. –

+1

Utiliza el CancelToken para cancelar las tareas. Y filtra la OperationCancelException específica que obtendrás ahora. –

Respuesta

28

El AggregateException proporciona un método Handle que se puede utilizar para estas situaciones. Si por ejemplo se desea ignorar TaskCanceledException que puede hacer:

var all = new AggregateException(
    new NullReferenceException(), 
    new TaskCanceledException(), 
    new TaskCanceledException(), 
    new InvalidOperationException(), 
    new TaskCanceledException()); 

try 
{ 
    throw all; 
} 
catch (AggregateException errors) 
{ 
    errors.Handle(e => e is TaskCanceledException); 
} 

Si todas las excepciones son de tipo TaskCanceledException, el método Handle no arrojará ninguna excepción; de lo contrario, se lanzará un nuevo AggregateException que contiene solo las excepciones no controladas.

1

Basado en João Angelo's suggestion, aquí va una extensión de clase de tareas

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 
using System.Threading.Tasks; 

namespace MySharedLibrary.Extensions 
{ 
    public static class TaskExtensions 
    { 

     // This code is based João Angelo's stackoverflow suggestion https://stackoverflow.com/a/8681687/378115 

     // Use this when a CancellationTokenSource is used 
     public static void SafeWait(this Task TargetTask, CancellationTokenSource TargetTaskCancellationTokenSource) 
     { 
      if (TargetTaskCancellationTokenSource.IsCancellationRequested == false) 
      { 
       TargetTaskCancellationTokenSource.Cancel(); 
      } 
      SafeWait(TargetTask); 
     } 

     // Use this when no CancellationTokenSource is used 
     public static void SafeWait(this Task TargetTask) 
     { 
      try 
      { 
       if (TargetTask.IsCanceled == false) 
       { 
        TargetTask.Wait(); 
       } 
      } 
      catch (AggregateException errors) 
      { 
       errors.Handle(e => e is TaskCanceledException); 
      } 
     } 

    } 
} 
Cuestiones relacionadas