6

Me gustaría comenzar a utilizar Task Parallel Library, ya que este es el marco recomendado en el futuro para realizar operaciones asincrónicas. Una cosa que no he podido encontrar es cualquier medio de Aborto forzoso, como lo que proporciona Thread.Abort.Eliminando una tarea bloqueada .NET 4 TPL

Mi preocupación particular es que programo tareas que ejecutan código en el que no deseo confiar por completo. En particular, no puedo estar seguro de que este código que no es de confianza no se estancará y, por lo tanto, no puedo estar seguro de si alguna vez se completará una planificación de Tarea I utilizando este código. Quiero mantenerme alejado del verdadero aislamiento de AppDomain (debido a la sobrecarga y la complejidad de las referencias), pero tampoco quiero dejar un thread de tarea rondando, estancado. ¿Hay alguna manera de hacer esto en TPL?

Respuesta

9

La forma de hacerlo es con un CancellationToken y el nuevo modelo de cancelación. El nuevo modelo de cancelación está integrado en .NET Framework en varios tipos. Los más importantes son System.Threading.Tasks, System .Threading.Tasks.Task, System.Threading.Tasks.Task y System.Linq.ParallelEnumerable.

Aquí hay un ejemplo de su problema. Este código siempre se estancará porque el código de llamada toma primero un bloqueo y luego la tarea interbloqueada intenta obtener el mismo bloqueo.

public void Example() 
{ 
    object sync = new Object(); 
    lock (sync) 
    { 
     CancellationTokenSource canceller = new CancellationTokenSource(); 
    ManualResetEvent started = new ManualResetEvent(false); 
     Task deadlocked = Task.Factory.StartNew(() => 
      { 
      started.Set(); 
       // EVIL CODE: This will ALWAYS deadlock 
       lock(sync) { }; 
      }, 
      canceller.Token); 

     // Make sure task has started. 
    started.WaitOne(); 

     canceller.Cancel(); 

     try 
     { 
      // Wait for task to cancel. 
      deadlocked.Wait(); 
     } 
     catch (AggregateException ex) 
     { 
      // Ignore canceled exception. SIMPLIFIED! 
      if (!(ex.InnerException is TaskCanceledException)) 
       throw; 
     } 
    } 
} 

La cancelación de tareas en el TPL es cooperativa. En otras palabras, esto siempre se estancará porque nada maneja el token de cancelación que se configura como cancelado porque el hilo de la tarea está bloqueado.

Hay una forma de evitar esto, pero todavía se basa en los autores del código no confiable para hacer lo correcto:

public static void Example2() 
{ 
    Mutex sync = new Mutex(true); 

    CancellationTokenSource canceller = new CancellationTokenSource(); 
    bool started = false; 

    Task deadlocked = Task.Factory.StartNew(() => 
     { 
      started = true; 
      // EVIL CODE: This will ALWAYS deadlock 
      WaitHandle.WaitAny(new WaitHandle[] { canceller.Token.WaitHandle, sync }); 
     }, 
     canceller.Token); 

    // Make sure task has started. 
    while (!started) { } 

    canceller.Cancel(); 

    try 
    { 
     // Wait for task to cancel. 
     deadlocked.Wait(); 
    } 
    catch (AggregateException ex) 
    { 
     // Ignore canceled exception. SIMPLIFIED! 
     if (!(ex.InnerException is TaskCanceledException)) 
      throw; 
    } 
} 

Puntos a tener en cuenta; la cancelación es cooperativa. Puede usar Token.WaitHandle para obtener un identificador y esperarlo junto con los identificadores de otras primitivas de sincronización. Mutex es mucho más lento que Monitor (o bloqueo).

Realmente, si no confías en el autor del código lo suficiente como para que implementen la cancelación cooperativa, entonces cuestionaría la cordura de que se ejecuten dentro de tu AppDomain en el mismo hilo.

Para más detalles ver:

http://msdn.microsoft.com/en-us/library/dd997364.aspx

http://msdn.microsoft.com/en-us/library/dd537607.aspx

http://msdn.microsoft.com/en-us/library/ee191552.aspx

+0

Gracias, esta es información útil. Sin embargo, tenía la impresión de que corresponde a la tarea escuchar la solicitud de cancelación y lanzar OperationCancelledException por sí solo si no se puede completar por completo. Voy a probar tu código cuando tenga oportunidad esta tarde. –

+0

Desde el primer enlace, "los oyentes pueden recibir notificaciones de solicitudes de cancelación por sondeo, registro de devolución de llamada o espera en los identificadores de espera". Tan eficazmente Task.Wait hace que la escucha ocurra. –

+0

Ver: http://stackoverflow.com/questions/2293976/how-and-if-to-write-a-single-consumer-queue-using-the-task-parallel-library/2779208#2779208 para ver un ejemplo de utilizando IsCancellationRequested para verificar la cancelación y responder a ella. –

-4

Simplemente llame al Task.Wait(timespanToWait).

Si la tarea no se completa después del intervalo de tiempo especificado, se cancela.

+0

Gracias. Esto no era del todo obvio a partir de la documentación, pero tiene sentido cuando piensas en las Tareas como aislarte de cualquier detalle sobre cómo se programan y administran las Tareas. ¿Sabes si tendré que hacer algo especial después de que Wait regresa antes de poder deshacerme de la tarea de forma segura? –

+11

Esta no es la respuesta. Wait() hará exactamente eso, espera. No cancelará/cancelará la tarea. –

0

Dan No creo que Task.Wait (timeout) cancelará esta tarea, hay Overload Task.Wait (timeout, cancelationToken), pero eso solo arroja OperationCanceledException en la tarea. Espera cuando se señaliza el token.

Tarea. Espere solo bloques hasta que se complete cualquiera de las tareas o el tiempo de espera caduque, no se cancela ni cancela la tarea. Una tarea tan estancada seguirá colgando en ThreadPool. No puede deshacerse de la tarea no completada (InvalidOperation).

Im escribiendo el mismo tipo de aplicación como usted y he escrito mi propia TaskScheduler que permite Abortar (y no está usando subprocesos :().

pero soy muy curioso acerca de cómo se resolvió este problema. Por favor responder para mí.

+0

Tras una consideración más detenida, decidí que si se producía un punto muerto, Thread.Abort realmente solo enmascara cualquier problema subyacente, ya que el estado puede haberse corrompido. Por lo tanto, considero que no terminar es fatal (el operador tiene la opción de seguir esperando o finalizar la aplicación). Esto es suficiente para mi aplicación, ya que no es una misión crítica; si la aplicación fuera crítica para la misión, tendría que migrar al uso de AppDomains o un proceso de alojamiento separado para el código parcialmente confiable. –

+0

Dan, creo que esta es la forma correcta de pensar sobre esto. Thread.Abort definitivamente no es una solución. Puede dejar su aplicación en un estado desconocido. –