¿Cómo aborto / cancelo las tareas TPL?

Respuestas:

228

No puedes Las tareas usan subprocesos de fondo del grupo de subprocesos. Tampoco se recomienda cancelar hilos usando el método Abortar. Puede echar un vistazo a la siguiente publicación de blog que explica una forma adecuada de cancelar tareas utilizando tokens de cancelación. Aquí hay un ejemplo:

class Program
{
    static void Main()
    {
        var ts = new CancellationTokenSource();
        CancellationToken ct = ts.Token;
        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                // do some heavy work here
                Thread.Sleep(100);
                if (ct.IsCancellationRequested)
                {
                    // another thread decided to cancel
                    Console.WriteLine("task canceled");
                    break;
                }
            }
        }, ct);

        // Simulate waiting 3s for the task to complete
        Thread.Sleep(3000);

        // Can't wait anymore => cancel this task 
        ts.Cancel();
        Console.ReadLine();
    }
}
Darin Dimitrov
fuente
55
Buena explicación Tengo una pregunta, ¿cómo funciona cuando no tenemos un método anónimo en Task.Factory.StartNew? como Task.Factory.StartNew (() => ProcessMyMethod (), cancellationToken)
Prerak K
61
¿Qué pasa si hay una llamada de bloqueo que no regresa dentro de la tarea de ejecución?
mehmet6parmak
3
@ mehmet6parmak Creo que lo único que puede hacer es usarlo Task.Wait(TimeSpan / int)para darle una fecha límite (basada en el tiempo) desde el exterior.
Mark
2
¿Qué sucede si tengo mi clase personalizada para administrar la ejecución de métodos dentro de una nueva Task? Algo así como: public int StartNewTask(Action method). Dentro del StartNewTaskmétodo se crea una nueva Taskpor: Task task = new Task(() => method()); task.Start();. Entonces, ¿cómo puedo gestionar el CancellationToken? También me gustaría saber si Threadnecesito implementar una lógica para verificar si hay algunas Tareas que aún están pendientes y así matarlas cuando Form.Closing. Con Threadslo uso Thread.Abort().
Cheshire Cat
¡Oh, qué mal ejemplo! Es una condición booleana simple, por supuesto, ¡es lo primero que se intentaría! Pero es decir, tengo una función en una tarea separada, que puede tardar mucho tiempo en finalizar, e idealmente no debería saber nada sobre un subproceso o lo que sea. Entonces, ¿cómo cancelo la función con su consejo?
Hola Ángel
32

Anular una tarea es fácilmente posible si captura el hilo en el que se ejecuta la tarea. Aquí hay un código de ejemplo para demostrar esto:

void Main()
{
    Thread thread = null;

    Task t = Task.Run(() => 
    {
        //Capture the thread
        thread = Thread.CurrentThread;

        //Simulate work (usually from 3rd party code)
        Thread.Sleep(1000);

        //If you comment out thread.Abort(), then this will be displayed
        Console.WriteLine("Task finished!");
    });

    //This is needed in the example to avoid thread being still NULL
    Thread.Sleep(10);

    //Cancel the task by aborting the thread
    thread.Abort();
}

Usé Task.Run () para mostrar el caso de uso más común para esto: usar la comodidad de Tareas con código antiguo de subproceso único, que no usa la clase CancellationTokenSource para determinar si debe cancelarse o no.

Florian Rappl
fuente
2
Gracias por esta idea Usé este enfoque para implementar un tiempo de espera para algún código externo, que no tiene CancellationTokensoporte ...
Christoph Fink
77
AFAIK thread.abort te dejará desconocido sobre tu pila, puede ser inválido. Nunca lo he intentado, pero supongo que comenzar un hilo en un dominio de aplicación separado que thread.abort se guardará. Además, se desperdicia un hilo completo en su solución solo para abortar una tarea. No tendría que usar tareas sino hilos en primer lugar. (
voto negativo
1
Como escribí, esta solución es un último recurso que podría considerarse bajo ciertas circunstancias. Por supuesto, se CancellationTokendeben considerar soluciones simples o incluso más simples que estén libres de condiciones de carrera. El código anterior solo ilustra el método, no el área de uso.
Florian Rappl
8
Creo que este enfoque podría tener consecuencias desconocidas y no lo recomendaría en el código de producción. No siempre se garantiza que las tareas se ejecuten en un subproceso diferente, lo que significa que se pueden ejecutar en el mismo subproceso que el que las creó si el planificador lo decide (lo que significa que el subproceso principal se pasará a la threadvariable local). En su código, puede terminar abortando el hilo principal, que no es lo que realmente quiere. Quizás sea una buena
idea
10
@ Martin: Al investigar esta pregunta en SO, veo que ha rechazado varias respuestas que usan Thread.Abort para eliminar tareas, pero no ha proporcionado ninguna solución alternativa. ¿Cómo puede eliminar el código de terceros que no admite la cancelación y se ejecuta en una tarea ?
Gordon Bean
32

Como sugiere esta publicación , esto se puede hacer de la siguiente manera:

int Foo(CancellationToken token)
{
    Thread t = Thread.CurrentThread;
    using (token.Register(t.Abort))
    {
        // compute-bound work here
    }
}

Aunque funciona, no se recomienda utilizar este enfoque. Si puede controlar el código que se ejecuta en la tarea, será mejor que se encargue de la cancelación adecuada.

Starteleport
fuente
66
+1 por dar un enfoque diferente mientras declara sus retrocesos. No sabía que esto podría hacerse :)
Joel
1
Gracias por la solucion! Simplemente podemos pasar el token al método y cancelar el tokensource, en lugar de obtener de alguna manera una instancia de hilo del método y abortar esta instancia directamente.
Sam
El subproceso de la tarea que se aborta puede ser un subproceso de grupo de subprocesos o el subproceso del llamador que invoca la tarea. Para evitar esto, puede usar un TaskScheduler para especificar un hilo dedicado para la tarea .
Edward Brey
19

Este tipo de cosas es una de las razones logísticas por las que Abortestá en desuso. En primer lugar, no use Thread.Abort()para cancelar o detener un hilo si es posible. Abort()solo debe usarse para matar a la fuerza un hilo que no responde a solicitudes más pacíficas para detenerse de manera oportuna.

Dicho esto, debe proporcionar un indicador de cancelación compartido de que un subproceso se establece y espera mientras el otro subproceso verifica periódicamente y sale correctamente. .NET 4 incluye una estructura diseñada específicamente para este propósito, el CancellationToken.

Adam Robinson
fuente
8

No debe intentar hacer esto directamente. Diseñe sus tareas para trabajar con un CancellationToken y cancélelas de esta manera.

Además, recomendaría cambiar su hilo principal para que funcione a través de un CancellationToken también. Llamar Thread.Abort()es una mala idea: puede provocar varios problemas que son muy difíciles de diagnosticar. En cambio, ese hilo puede usar la misma Cancelación que usan sus tareas, y lo mismo CancellationTokenSourcepuede usarse para desencadenar la cancelación de todas sus tareas y su hilo principal.

Esto conducirá a un diseño mucho más simple y seguro.

Reed Copsey
fuente
8

Para responder la pregunta de Prerak K sobre cómo usar CancellationTokens cuando no se usa un método anónimo en Task.Factory.StartNew (), se pasa el CancellationToken como parámetro al método que está comenzando con StartNew (), como se muestra en el ejemplo de MSDN aquí .

p.ej

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

Task.Factory.StartNew( () => DoSomeWork(1, token), token);

static void DoSomeWork(int taskNum, CancellationToken ct)
{
    // Do work here, checking and acting on ct.IsCancellationRequested where applicable, 

}
Jools
fuente
7

Utilizo un enfoque mixto para cancelar una tarea.

  • En primer lugar, estoy tratando de cancelarlo cortésmente con el uso de la cancelación .
  • Si todavía se está ejecutando (por ejemplo, debido a un error del desarrollador), entonces compórtate mal y mátalo usando un método de cancelación de la vieja escuela .

Vea un ejemplo a continuación:

private CancellationTokenSource taskToken;
private AutoResetEvent awaitReplyOnRequestEvent = new AutoResetEvent(false);

void Main()
{
    // Start a task which is doing nothing but sleeps 1s
    LaunchTaskAsync();
    Thread.Sleep(100);
    // Stop the task
    StopTask();
}

/// <summary>
///     Launch task in a new thread
/// </summary>
void LaunchTaskAsync()
{
    taskToken = new CancellationTokenSource();
    Task.Factory.StartNew(() =>
        {
            try
            {   //Capture the thread
                runningTaskThread = Thread.CurrentThread;
                // Run the task
                if (taskToken.IsCancellationRequested || !awaitReplyOnRequestEvent.WaitOne(10000))
                    return;
                Console.WriteLine("Task finished!");
            }
            catch (Exception exc)
            {
                // Handle exception
            }
        }, taskToken.Token);
}

/// <summary>
///     Stop running task
/// </summary>
void StopTask()
{
    // Attempt to cancel the task politely
    if (taskToken != null)
    {
        if (taskToken.IsCancellationRequested)
            return;
        else
            taskToken.Cancel();
    }

    // Notify a waiting thread that an event has occurred
    if (awaitReplyOnRequestEvent != null)
        awaitReplyOnRequestEvent.Set();

    // If 1 sec later the task is still running, kill it cruelly
    if (runningTaskThread != null)
    {
        try
        {
            runningTaskThread.Join(TimeSpan.FromSeconds(1));
        }
        catch (Exception ex)
        {
            runningTaskThread.Abort();
        }
    }
}
Alex Klaus
fuente
4

Las tareas tienen soporte de primera clase para la cancelación a través de tokens de cancelación . Cree sus tareas con tokens de cancelación y cancele las tareas a través de estos explícitamente.

Tim Lloyd
fuente
4

Puede usar a CancellationTokenpara controlar si la tarea se cancela. ¿Estás hablando de abortarlo antes de que comience ("no importa, ya hice esto") o de interrumpirlo en el medio? Si es lo primero, CancellationTokenpuede ser útil; si es lo último, probablemente necesitará implementar su propio mecanismo de "rescate" y verificar en los puntos apropiados en la ejecución de la tarea si debe fallar rápidamente (aún puede usar CancellationToken para ayudarlo, pero es un poco más manual).

MSDN tiene un artículo sobre la cancelación de tareas: http://msdn.microsoft.com/en-us/library/dd997396.aspx

Madeja
fuente
3

Las tareas se ejecutan en ThreadPool (al menos, si está utilizando la fábrica predeterminada), por lo que abortar el hilo no puede afectar las tareas. Para abortar tareas, vea Cancelación de tareas en msdn.

Oliver Hanappi
fuente
1

Lo intenté CancellationTokenSourcepero no puedo hacer esto. Y lo hice a mi manera. Y funciona.

namespace Blokick.Provider
{
    public class SignalRConnectProvider
    {
        public SignalRConnectProvider()
        {
        }

        public bool IsStopRequested { get; set; } = false; //1-)This is important and default `false`.

        public async Task<string> ConnectTab()
        {
            string messageText = "";
            for (int count = 1; count < 20; count++)
            {
                if (count == 1)
                {
                //Do stuff.
                }

                try
                {
                //Do stuff.
                }
                catch (Exception ex)
                {
                //Do stuff.
                }
                if (IsStopRequested) //3-)This is important. The control of the task stopping request. Must be true and in inside.
                {
                    return messageText = "Task stopped."; //4-) And so return and exit the code and task.
                }
                if (Connected)
                {
                //Do stuff.
                }
                if (count == 19)
                {
                //Do stuff.
                }
            }
            return messageText;
        }
    }
}

Y otra clase de llamar al método:

namespace Blokick.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MessagePerson : ContentPage
    {
        SignalRConnectProvider signalR = new SignalRConnectProvider();

        public MessagePerson()
        {
            InitializeComponent();

            signalR.IsStopRequested = true; // 2-) And this. Make true if running the task and go inside if statement of the IsStopRequested property.

            if (signalR.ChatHubProxy != null)
            {
                 signalR.Disconnect();
            }

            LoadSignalRMessage();
        }
    }
}
Hasan Tuna Oruç
fuente
0

Puede abortar una tarea como un hilo si puede hacer que la tarea se cree en su propio hilo y llamar Aborta su Threadobjeto. De forma predeterminada, una tarea se ejecuta en un subproceso de grupo de subprocesos o en el subproceso de llamada, ninguno de los cuales normalmente desea anular.

Para garantizar que la tarea tenga su propio hilo, cree un planificador personalizado derivado de TaskScheduler. En su implementación de QueueTask, cree un nuevo hilo y úselo para ejecutar la tarea. Más tarde, puede abortar el subproceso, lo que hará que la tarea se complete en un estado defectuoso con un ThreadAbortException.

Use este programador de tareas:

class SingleThreadTaskScheduler : TaskScheduler
{
    public Thread TaskThread { get; private set; }

    protected override void QueueTask(Task task)
    {
        TaskThread = new Thread(() => TryExecuteTask(task));
        TaskThread.Start();
    }

    protected override IEnumerable<Task> GetScheduledTasks() => throw new NotSupportedException(); // Unused
    protected override bool NotSupportedException(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException(); // Unused
}

Comience su tarea así:

var scheduler = new SingleThreadTaskScheduler();
var task = Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.LongRunning, scheduler);

Más tarde, puedes abortar con:

scheduler.TaskThread.Abort();

Tenga en cuenta que la advertencia sobre abortar un hilo todavía se aplica:

El Thread.Abortmétodo debe usarse con precaución. Particularmente cuando lo llama para abortar un hilo que no sea el hilo actual, no sabe qué código se ha ejecutado o no se pudo ejecutar cuando se lanza ThreadAbortException , ni puede estar seguro del estado de su aplicación o de cualquier aplicación y estado de usuario que es responsable de preservar. Por ejemplo, llamar Thread.Abortpuede evitar que los constructores estáticos se ejecuten o evitar la liberación de recursos no administrados.

Edward Brey
fuente
Este código falla con una excepción de tiempo de ejecución: System.InvalidOperationException: RunSynchronously no se puede invocar en una tarea que ya se inició.
Theodor Zoulias
1
@TheodorZoulias Buena captura. Gracias. Arreglé el código y, en general, mejoré la respuesta.
Edward Brey
1
Sí, esto solucionó el error. Otra advertencia de que probablemente debería mencionarse es que Thread.Abortno es compatible con .NET Core. Intentar usarlo da como resultado una excepción: System.PlatformNotSupportedException: Thread abort no es compatible con esta plataforma. Una tercera advertencia es que SingleThreadTaskSchedulerno se puede usar de manera efectiva con tareas de estilo prometedor, en otras palabras, con tareas creadas con asyncdelegados. Por ejemplo, un incrustado se await Task.Delay(1000)ejecuta en ningún subproceso, por lo que no se ve afectado por los eventos de subproceso.
Theodor Zoulias