Storno-Token im Task-Konstruktor: Warum?

223

Bestimmte System.Threading.Tasks.TaskKonstruktoren verwenden a CancellationTokenals Parameter:

CancellationTokenSource source = new CancellationTokenSource();
Task t = new Task (/* method */, source.Token);

Was mich daran verblüfft, ist, dass es innerhalb des Methodenkörpers keine Möglichkeit gibt , tatsächlich an das übergebene Token zu gelangen (z. B. nichts dergleichen Task.CurrentTask.CancellationToken). Das Token muss über einen anderen Mechanismus bereitgestellt werden, z. B. das Statusobjekt oder in einem Lambda erfasst.

Welchen Zweck erfüllt die Bereitstellung des Stornierungs-Tokens im Konstruktor?

Colin
quelle

Antworten:

254

Durch Übergeben von a CancellationTokenan den TaskKonstruktor wird es der Aufgabe zugeordnet.

Zitiert Stephen Toubs Antwort von MSDN :

Dies hat zwei Hauptvorteile:

  1. Wenn für das Token vor dem TaskStart eine Ausführung angefordert wurde, wird die Ausführung Tasknicht ausgeführt. Anstatt zu wechseln, Runningwird es sofort zu wechseln Canceled. Dadurch werden die Kosten für die Ausführung der Aufgabe vermieden, wenn sie ohnehin nur während der Ausführung abgebrochen wird.
  2. Wenn der Hauptteil der Aufgabe auch das Stornierungs-Token überwacht und ein Token auslöst, OperationCanceledExceptiondas dieses Token enthält (was auch der ThrowIfCancellationRequestedFall ist), OperationCanceledExceptionprüft die Aufgabe, wenn sie dies sieht, ob das OperationCanceledExceptionToken des Tokens mit dem Token der Aufgabe übereinstimmt. Wenn dies der Fall ist, wird diese Ausnahme als Bestätigung der kooperativen Kündigung und der TaskÜbergänge zum Canceled Staat (und nicht zum FaultedStaat) angesehen.
Max Galkin
quelle
2
Die TPL ist so gut durchdacht.
Colonel Panic
1
Ich gehe davon aus, dass Vorteil 1 in ähnlicher Weise für die Übergabe eines Stornierungsscheins an Parallel.ForoderParallel.ForEach
Colonel Panic
27

Der Konstruktor verwendet das Token für die interne Abbruchbehandlung. Wenn Ihr Code Zugriff auf das Token haben möchte, sind Sie dafür verantwortlich, es an sich selbst weiterzugeben. Ich würde wärmstens empfehlen, das Buch Parallele Programmierung mit Microsoft .NET bei CodePlex zu lesen .

Beispiel für die Verwendung von CTS aus dem Buch:

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task myTask = Task.Factory.StartNew(() =>
{
    for (...)
    {
        token.ThrowIfCancellationRequested();

        // Body of for loop.
    }
}, token);

// ... elsewhere ...
cts.Cancel();
user7116
quelle
3
und was passiert, wenn Sie kein Token als Parameter übergeben? Sieht so aus, als wäre das Verhalten dasselbe, kein Zweck.
Sergtk
2
@sergdev: Sie übergeben das Token, um es bei der Task und dem Scheduler zu registrieren. Es nicht zu bestehen und es zu benutzen wäre undefiniertes Verhalten.
user7116
3
@sergdev: nach dem Testen: myTask.IsCanceled und myTask.Status sind nicht identisch, wenn Sie das Token nicht als Parameter übergeben. Der Status wird fehlgeschlagen anstatt abgebrochen. Die Ausnahme ist jedoch dieselbe: In beiden Fällen handelt es sich um eine OperationCanceledException.
Olivier de Rivoyre
2
Was ist, wenn ich nicht anrufe token.ThrowIfCancellationRequested();? In meinem Test ist das Verhalten das gleiche. Irgendwelche Ideen?
Machinarium
1
@ CobaltBlue: when cts.Cancel() is called the Task is going to get canceled and end, no matter what you doNein. Wenn die Aufgabe vor dem Start abgebrochen wird, wird sie abgebrochen . Wenn der Hauptteil der Aufgabe niemals ein Token überprüft, wird es vollständig ausgeführt , was zu einem RanToCompletion- Status führt. Wenn der Body ein OperationCancelledExceptionz. B. von ThrowIfCancellationRequestedauslöst, prüft Task, ob das CancellationToken dieser Ausnahme mit dem mit der Task verknüpften identisch ist. Wenn dies der Fall ist, wird die Aufgabe abgebrochen . Wenn nicht, ist es fehlerhaft .
Wolfzoon
7

Stornierung ist kein einfacher Fall, wie viele vielleicht denken. Einige der Feinheiten werden in diesem Blog-Beitrag auf msdn erklärt:

Beispielsweise:

In bestimmten Situationen in parallelen Erweiterungen und in anderen Systemen muss eine blockierte Methode aus Gründen aktiviert werden, die nicht auf eine explizite Kündigung durch einen Benutzer zurückzuführen sind. Wenn beispielsweise ein Thread blockiert ist, blockingCollection.Take()weil die Sammlung leer ist und ein anderer Thread anschließend aufruft blockingCollection.CompleteAdding(), sollte der erste Aufruf aktiviert werden und einen auslösen InvalidOperationException, um eine falsche Verwendung darzustellen.

Stornierung in parallelen Erweiterungen

x0n
quelle
4

Hier ist ein Beispiel, das die beiden Punkte in der Antwort von Max Galkin demonstriert :

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(true);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(true);
        Console.WriteLine();

        Console.WriteLine();
        Console.WriteLine("Test Done!!!");
        Console.ReadKey();
    }

    static void StartCanceledTaskTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, false), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, false));
        }

        Console.WriteLine("Canceling task");
        tokenSource.Cancel();

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void ThrowIfCancellationRequestedTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, true), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, true));
        }

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            Thread.Sleep(100);

            Console.WriteLine("Canceling task");
            tokenSource.Cancel();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void TaskWork(CancellationToken token, bool throwException)
    {
        int loopCount = 0;

        while (true)
        {
            loopCount++;
            Console.WriteLine("Task: loop count {0}", loopCount);

            token.WaitHandle.WaitOne(50);
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Task: cancellation requested");
                if (throwException)
                {
                    token.ThrowIfCancellationRequested();
                }

                break;
            }
        }
    }
}

Ausgabe:

*********************************************************************
* Start canceled task, don't pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Task: loop count 1
Task: cancellation requested
Task.Status: RanToCompletion

*********************************************************************
* Start canceled task, pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Exception: Start may not be called on a task that has completed.
Task.Status: Canceled

*********************************************************************
* Throw if cancellation requested, don't pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: The operation was canceled.
Task.Status: Faulted

*********************************************************************
* Throw if cancellation requested, pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: A task was canceled.
Task.Status: Canceled


Test Done!!!
Eliahu Aaron
quelle