web-dev-qa-db-fra.com

Comment puis-je abandonner/annuler des tâches TPL?

Dans un fil de discussion, je crée un System.Threading.Task et lance chaque tâche.

Lorsque je fais .Abort() pour tuer le thread, les tâches ne sont pas abandonnées.

Comment puis-je transmettre la .Abort() à mes tâches?

136
Patrice Pezillier

Tu ne peux pas. Les tâches utilisent des threads d'arrière-plan du pool de threads. Annuler également les threads à l'aide de la méthode Abort n'est pas recommandé. Vous pouvez jeter un oeil au blog suivant qui explique comment annuler correctement des tâches à l'aide de jetons d'annulation. Voici un exemple:

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();
    }
}
202
Darin Dimitrov

Abandonner une tâche est facilement possible si vous capturez le fil dans lequel la tâche est en cours d'exécution. Voici un exemple de code pour illustrer ceci:

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();
}

J'ai utilisé Task.Run () pour montrer le cas d'utilisation le plus courant: utiliser le confort des tâches avec l'ancien code à thread unique, qui n'utilise pas la classe CancellationTokenSource pour déterminer s'il doit être annulé ou non.

27
Florian Rappl

Comme cet article suggère, cela peut être fait de la manière suivante:

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

Bien que cela fonctionne, il n'est pas recommandé d'utiliser une telle approche. Si vous pouvez contrôler le code qui s'exécute dans une tâche, vous feriez mieux de gérer correctement l'annulation.

25
starteleport

Ce genre de chose est l’une des raisons logistiques pour lesquelles Abort est déconseillé. Tout d’abord, n’utilisez pas Thread.Abort() pour annuler ou arrêter un thread, dans la mesure du possible.Abort() ne doit être utilisé que pour tuer de force un thread qui ne répond pas à des demandes plus pacifiques d'arrêter en temps voulu.

Cela dit, vous devez fournir un indicateur d'annulation partagée qu'un thread définit et attend pendant que l'autre thread vérifie et se termine régulièrement. .NET 4 inclut une structure spécialement conçue à cet effet, le CancellationToken .

17
Adam Robinson

Vous ne devriez pas essayer de le faire directement. Concevez vos tâches avec un CancellationToken et annulez-les de cette façon.

En outre, je vous recommanderais de modifier votre thread principal pour qu'il fonctionne également via un CancellationToken. Appeler Thread.Abort() est une mauvaise idée. Cela peut entraîner divers problèmes très difficiles à diagnostiquer. À la place, ce fil peut utiliser le même Annulation que vos tâches utilisent - et la même CancellationTokenSource peuvent être utilisés pour déclencher l'annulation de all de vos tâches et de votre fil principal.

Cela conduira à une conception beaucoup plus simple et plus sûre.

7
Reed Copsey

Pour répondre à la question de Prerak K sur la manière d'utiliser AnnulationTokens lorsque vous n'utilisez pas de méthode anonyme dans Task.Factory.StartNew (), vous transmettez AnnulationToken en tant que paramètre à la méthode que vous démarrez avec StartNew (), comme indiqué dans l'exemple MSDN ici .

par exemple.

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, 

}
7
Jools

J'utilise une approche mixte pour annuler une tâche. 

  • Tout d'abord, j'essaie de l'annuler poliment avec l'utilisation de Cancellation
  • S'il est toujours en cours d'exécution (par exemple, en raison d'une erreur du développeur), agissez mal et tuez-le à l'aide d'une méthode old-school Abort .

Commander un exemple ci-dessous:

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();
        }
    }
}
5
Alex Klaus

Vous pouvez utiliser une CancellationToken pour contrôler si la tâche est annulée. Parlez-vous de l'avorter avant de commencer ("tant pis, je l'ai déjà fait"), ou de l'interrompre au milieu? Si c'est le cas, la CancellationToken peut être utile; Dans ce dernier cas, vous devrez probablement mettre en œuvre votre propre mécanisme de "sauvetage" et vérifier aux étapes appropriées de l'exécution de la tâche si vous devez échouer rapidement (vous pouvez toujours utiliser CancellationToken pour vous aider, mais c'est un peu plus manuel).

MSDN a publié un article sur l'annulation des tâches: http://msdn.Microsoft.com/en-us/library/dd997396.aspx

3
Hank

Les tâches sont en cours d'exécution sur le ThreadPool (au moins si vous utilisez la fabrique par défaut), si bien que l'abandon du thread ne peut pas affecter les tâches. Pour annuler des tâches, voir Annulation de tâche sur msdn.

2
Oliver Hanappi

Les tâches ont un support de première classe pour l'annulation via jetons d'annulation . Créez vos tâches avec des jetons d'annulation et annulez-les explicitement.

2
Tim Lloyd

J'ai essayé CancellationTokenSource mais je ne peux pas faire ça. Et j'ai fait cela à ma manière. Et il fonctionne.

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;
        }
    }
}

Et une autre classe de l'appelant la méthode:

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();
        }
    }
}
0
Hasan Tuna Oruç