¿Es posible abortar una tarea como abortar un hilo (método Thread.Abort)?

Podríamos abortar un hilo como este:

Thread thread = new Thread(SomeMethod); . . . thread.Abort(); 

Pero, ¿puedo cancelar una Tarea (en .Net 4.0) de la misma manera, no por un mecanismo de cancelación? Quiero matar la Tarea de inmediato.

  1. No deberías usar Thread.Abort ()
  2. Las tareas pueden ser canceladas pero no canceladas.

El método Thread.Abort () está (severamente) obsoleto.

Tanto los hilos como las tareas deben cooperar al detenerse, de lo contrario correrá el riesgo de dejar el sistema en un estado inestable / indefinido.

Si necesita ejecutar un Proceso y matarlo desde el exterior, la única opción segura es ejecutarlo en un Dominio de Aplicación separado.

La guía sobre no utilizar un aborto de subprocesos es controvertida. Creo que todavía hay un lugar para ello, pero en circunstancias excepcionales. Sin embargo, siempre debe intentar diseñar a su alrededor y verlo como último recurso.

Ejemplo;

Tiene una aplicación de formulario de Windows simple que se conecta a un servicio web síncrono de locking. Dentro de la cual ejecuta una función en el servicio web dentro de un ciclo Paralelo.

 CancellationTokenSource cts = new CancellationTokenSource(); ParallelOptions po = new ParallelOptions(); po.CancellationToken = cts.Token; po.MaxDegreeOfParallelism = System.Environment.ProcessorCount; Parallel.ForEach(iListOfItems, po, (item, loopState) => { Thread.Sleep(120000); // pretend web service call }); 

Digamos en este ejemplo que la llamada de locking tarda 2 minutos en completarse. Ahora configuro mi MaxDegreeOfParallelism para decir ProcessorCount. iListOfItems tiene 1000 elementos para procesar.

El usuario hace clic en el botón de proceso y el ciclo comienza, tenemos ‘hasta’ 20 hilos ejecutándose contra 1000 artículos en la colección iListOfItems. Cada iteración se ejecuta en su propio hilo. Cada subproceso utilizará un subproceso en primer plano cuando lo cree Parallel.ForEach. Esto significa que independientemente del cierre de la aplicación principal, el dominio de la aplicación se mantendrá activo hasta que todos los hilos hayan terminado.

Sin embargo, el usuario debe cerrar la aplicación por algún motivo, es decir, cierra el formulario. Estos 20 hilos continuarán ejecutándose hasta que se procesen los 1000 elementos. Esto no es ideal en este escenario, ya que la aplicación no se cerrará como espera el usuario y continuará ejecutándose detrás de escena, como se puede ver al echar un vistazo al administrador de tareas.

Digamos que el usuario intenta reconstruir la aplicación de nuevo (VS 2010), informa que el exe está bloqueado, luego tendría que ir al administrador de tareas para matarlo o simplemente esperar hasta que se procesen los 1000 elementos.

No te culpo por decirlo, pero por supuesto! Debería cancelar estos subprocesos utilizando el objeto CancellationTokenSource y llamando a Cancelar … pero hay algunos problemas con esto desde .net 4.0. En primer lugar, esto nunca dará como resultado un aborto de subprocesos que ofrecería una excepción de aborto seguida de la terminación de subprocesos, por lo que el dominio de la aplicación deberá esperar a que los subprocesos finalicen normalmente, y esto significa esperar la última llamada de locking. que sería la última iteración en ejecución (subproceso) que finalmente llama a po.CancellationToken.ThrowIfCancellationRequested . En el ejemplo, esto significa que el dominio de la aplicación aún podría permanecer con vida durante hasta 2 minutos, aunque el formulario se haya cerrado y se haya cancelado la llamada.

Tenga en cuenta que la cancelación de llamada en CancellationTokenSource no arroja una excepción en el (los) thread (s) de procesamiento, que de hecho actuaría para interrumpir la llamada de locking similar a un aborto de subproceso y detener la ejecución. Una excepción se almacena en caché lista para cuando todos los otros subprocesos (iteraciones concurrentes) finalmente finalizan y regresan, la excepción se lanza en el subproceso iniciador (donde se declara el ciclo).

Elegí no usar la opción Cancelar en un objeto CancellationTokenSource. Esto es un desperdicio y podría decirse que infringe el conocido anti-patten de controlar el flujo del código por Excepciones.

En su lugar, es posiblemente “mejor” implementar una propiedad segura de subprocesos simple, es decir, Bool stopExecuting. Luego dentro del ciclo, verifique el valor de stopExecuting y si el valor se establece en verdadero por la influencia externa, podemos tomar una ruta alternativa para cerrar con gracia. Como no debemos llamar a cancelar, esto impide verificar CancellationTokenSource.IsCancellationRequested, que de lo contrario sería otra opción.

Algo como lo siguiente si la condición fuera apropiada dentro del ciclo;

if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || stopExecuting) {loopState.Stop (); regreso;}

La iteración ahora saldrá de una manera “controlada” así como también terminará otras iteraciones, pero como dije, esto hace poco para nuestro problema de tener que esperar en las llamadas de larga ejecución y locking que se realizan dentro de cada iteración ( hilo de bucle paralelo), ya que estos deben completarse antes de que cada hilo pueda obtener la opción de comprobar si debe detenerse.

En resumen, cuando el usuario cierra el formulario, se indicará que los 20 subprocesos se detengan mediante stopExecuting, pero solo se detendrán cuando hayan terminado de ejecutar su llamada de función de larga ejecución.

No podemos hacer nada sobre el hecho de que el dominio de la aplicación siempre permanecerá vivo y solo se liberará cuando todos los hilos de primer plano se hayan completado. Y esto significa que habrá un retraso asociado con la espera de que se completen las llamadas de locking realizadas dentro del ciclo.

Solo un aborto de un hilo verdadero puede interrumpir la llamada de locking, y debe mitigar dejar el sistema en un estado inestable / indefinido lo mejor que pueda en el controlador de excepciones del hilo anulado, lo cual no tiene dudas. Si eso es apropiado es una cuestión que el progtwigdor debe decidir, en función de qué recursos maneja mantener y qué tan fácil es cerrarlos en el bloque final de un hilo. Puedes registrarte con un token para terminar cancelando como una solución parcial, es decir,

 CancellationTokenSource cts = new CancellationTokenSource(); ParallelOptions po = new ParallelOptions(); po.CancellationToken = cts.Token; po.MaxDegreeOfParallelism = System.Environment.ProcessorCount; Parallel.ForEach(iListOfItems, po, (item, loopState) => { using (cts.Token.Register(Thread.CurrentThread.Abort)) { Try { Thread.Sleep(120000); // pretend web service call } Catch(ThreadAbortException ex) { // log etc. } Finally { // clean up here } } }); 

pero esto aún resultará en una excepción en el hilo de statement.

Teniendo todo en cuenta, interrumpa el locking de llamadas utilizando las construcciones parallel.loop podría haber sido un método sobre las opciones, evitando el uso de partes más oscuras de la biblioteca. Pero por qué no hay una opción para cancelar y evitar lanzar una excepción en el método de statement me parece un posible descuido.

Pero, ¿puedo cancelar una Tarea (en .Net 4.0) de la misma manera, no por un mecanismo de cancelación? Quiero matar la Tarea de inmediato .

Otros respondedores le han dicho que no lo haga. Pero sí, puedes hacerlo. Puede suministrar Thread.Abort() como el delegado al que llamará el mecanismo de cancelación de la Tarea. Aquí es cómo puede configurar esto:

 class HardAborter { public bool WasAborted { get; private set; } private CancellationTokenSource Canceller { get; set; } private Task Worker { get; set; } public void Start(Func DoFunc) { WasAborted = false; // start a task with a means to do a hard abort (unsafe!) Canceller = new CancellationTokenSource(); Worker = Task.Factory.StartNew(() => { try { // specify this thread's Abort() as the cancel delegate using (Canceller.Token.Register(Thread.CurrentThread.Abort)) { return DoFunc(); } } catch (ThreadAbortException) { WasAborted = true; return false; } }, Canceller.Token); } public void Abort() { Canceller.Cancel(); } } 

descargo de responsabilidad : no hagas esto.

Aquí hay un ejemplo de lo que no debe hacer:

  var doNotDoThis = new HardAborter(); // start a thread writing to the console doNotDoThis.Start(() => { while (true) { Thread.Sleep(100); Console.Write("."); } return null; }); // wait a second to see some output and show the WasAborted value as false Thread.Sleep(1000); Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted); // wait another second, abort, and print the time Thread.Sleep(1000); doNotDoThis.Abort(); Console.WriteLine("Abort triggered at " + DateTime.Now); // wait until the abort finishes and print the time while (!doNotDoThis.WasAborted) { Thread.CurrentThread.Join(0); } Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted + " at " + DateTime.Now); Console.ReadKey(); 

salida del código de muestra

Si bien es posible abortar un hilo, en la práctica casi siempre es una muy mala idea hacerlo. Abortar un hilo significa que el hilo no tiene la oportunidad de limpiarse por sí mismo, dejando los recursos recuperados y cosas en estados desconocidos.

En la práctica, si abortas un hilo, solo deberías hacerlo junto con matar el proceso. Tristemente, demasiadas personas piensan que ThreadAbort es una forma viable de detener algo y continuar, no lo es.

Dado que las tareas se ejecutan como hilos, puede llamar a ThreadAbort en ellas, pero como con los hilos generics, casi nunca desea hacer esto, excepto como último recurso.

 using System; using System.Threading; using System.Threading.Tasks; ... var cts = new CancellationTokenSource(); var task = Task.Run(() => { while (true) { } }); Parallel.Invoke(() => { task.Wait(cts.Token); }, () => { Thread.Sleep(1000); cts.Cancel(); }); 

Este es un fragmento simple para abortar una tarea interminable con CancellationTokenSource .

Si tiene el constructor de Tarea, entonces podemos extraer el Subproceso de la Tarea e invocar thread.abort.

 Thread th = null; Task.Factory.StartNew(() => { th = Thread.CurrentThread; while (true) { Console.WriteLine(DateTime.UtcNow); } }); Thread.Sleep(2000); th.Abort(); Console.ReadKey();