Multithreading in C#, Teil 22: Beenden von Tasks

In einem vorhergehenden Artikel habe ich Ihnen gezeigt wie sie Tasks erstellen und starten können. Darauf aufbauen möchte ich Ihnen nun näher bringen wie Sie auf das Beenden von Tasks warten können und wie Sie ein vorzeitiges Abbrechen von Tasks erzwingen können.

 
Auf das Beenden eines bestimmten Tasks warten

Mit Hilfe der Funktion Wait kann auf das Ende eines Tasks gewartet werden. Der Aufruf der Funktion führt dazu, dass an dieser Codezeile erst fortgesetzt wird, wenn der Task beendet wurde. Den gleichen Effekt können Sie erzielen indem Sie das Result Property des Tasks lesen. Der nachfolgende Quellcode zeigt die Verwendung der Wait Funktion.

class Program
{
    static void Main(string[] args)
    {
        Task task = Task.Factory.StartNew(DoWork);

        task.Wait();
        Console.WriteLine("Task finished");

        Console.ReadKey();
    }

    static void DoWork()
    {
        Console.WriteLine("Hello World");
        Thread.Sleep(TimeSpan.FromSeconds(3));
    }
}

 
Auf das Beenden mehrerer Tasks warten

Wenn Sie mehrere parallele Tasks gestartet haben und warten wollen bis alle abgeschlossen sind, dann müssen Sie hierzu keine einzelnen Aufrufe der Wait Funktion einsetzen. Einen eleganteren Weg bietet Ihnen die WaitAll Funktion der Task Klasse.

class Program
{
    static void Main(string[] args)
    {
        Task task1 = Task.Factory.StartNew(() => Console.WriteLine("Hello Task 1"));
        Task task2 = Task.Factory.StartNew(DoWork);

        Task.WaitAll(task1, task2);

        Console.WriteLine("Tasks finished");

        Console.ReadKey();
    }

    static void DoWork()
    {
        Console.WriteLine("Hello Task 2");
        Thread.Sleep(TimeSpan.FromSeconds(3));
    }
}

 
Anhand der Ausgabe der Konsolenapplikation sehen Sie, dass erst beide Tasks beendet werden bevor fortgesetzt wird. Die Ausgabe der Anwendung lautet folgendermassen:

Hello Task 1

Hello Task 2

Tasks finished

 
Auf das Beenden eines beliebigen Tasks warten

Im vorhergehenden Beispiel haben Sie gesehen wie auf das Beenden aller Tasks gewartet werden kann. Ein anderes typisches Szenario besteht darin zu warten, dass ein beliebiger Task aus einem Task Pool beendet wird. Der nachfolgende Quellcode zeigt das entsprechend angepasste Beispiel. Statt der Funktion WaitAll wird hierfür die Funktion WaitAny verwendet.

class Program
{
    static void Main(string[] args)
    {
        Task task1 = Task.Factory.StartNew(() => Console.WriteLine("Hello Task 1"));
        Task task2 = Task.Factory.StartNew(DoWork);

        Task.WaitAny(task1, task2);

        Console.WriteLine("One task finished");

        Console.ReadKey();
    }

    static void DoWork()
    {
        Console.WriteLine("Hello Task 2");
        Thread.Sleep(TimeSpan.FromSeconds(3));
    }
}

 
Anhand der Ausgabe der Konsolenapplikation sehen Sie, dass fortgesetzt wird sobald der erste Task beendet wurde. Die Ausgabe der Anwendung lautet folgendermassen:

Hello Task 1

One task finished

Hello Task 2

 
Auf das Beenden eines Nested Task warten

Mit einem „Nested Task“ meine ich einen Task der innerhalb eines anderen Tasks gestartet wurde. Dabei wird keine Beziehung zwischen diesen Tasks hergestellt. Beim Warten auf das Beenden verhalten sich Nested Tasks daher wie normale Tasks. Das folgende Beispiel zeigt eine entsprechende Konsolenanwendung. Ein Task startet intern einen weiteren Task der zur Veranschaulichung eine Pause von drei Sekunden durchführt. Die Hauptanwendung wartet auf den übergeordneten Task.

class Program
{
    static void Main(string[] args)
    {
        Task task = Task.Factory.StartNew(DoWork);

        task.Wait();
        Console.WriteLine("Task finished");

        Console.ReadKey();
    }

    static void DoWork()
    {
        Console.WriteLine("Main Task");

        Task.Factory.StartNew(NestedTask);
    }

    static void NestedTask()
    {
        Console.WriteLine("Nested Task");
        Thread.Sleep(TimeSpan.FromSeconds(3));
    }
}

 
Anhand der Ausgabe der Anwendung wird ersichtlich, dass nur auf den übergeordneten Task gewartet wird, unabhängig davon ob dieser intern weitere Tasks startet. Die Ausgabe der Anwendung lautet:

Main Task

Task finished

Nested Task

 
Auf das Beenden eines Child Task warten

Als „Child Task“ ist ein Task zu verstehen, der innerhalb eines anderen Tasks gestartet wurde und explizit eine Beziehung zum übergeordneten „Parent Task“ herstellt. Dazu wird AttachedToParent als TaskCreationOptions angegeben. Der nachfolgende Quellcode zeigt das angepasste Beispielprogramm. Dieses entspricht dem zuvor gezeigten Programm erweitert um den TaskCreationOptions Parameter.

class Program
{
    static void Main(string[] args)
    {
        Task task = Task.Factory.StartNew(DoWork);

        task.Wait();
        Console.WriteLine("Task finished");

        Console.ReadKey();
    }

    static void DoWork()
    {
        Console.WriteLine("Main Task");

        Task.Factory.StartNew(NestedTask, TaskCreationOptions.AttachedToParent);
    }

    static void NestedTask()
    {
        Console.WriteLine("Child Task");
        Thread.Sleep(TimeSpan.FromSeconds(3));
    }
}

 
Die Ausgabe der Konsolenanwendung ist nachfolgend zu sehen. Sie können sehen, dass sich die Ausgabe verglichen mit dem vorhergehenden Beispiel geändert hat. Beim Warten auf den Task wird nun explizit auf das Beenden aller Child Tasks gewartet.

Main Task

Child Task

Task finished

 
Vorzeitiges Abbrechen eines Tasks

Bisher habe ich Ihnen gezeigt wie Sie auf das reguläre Ende eines Tasks warten können. Sie können Task aber selbstverständlich auch vorzeitig beenden. Dazu wird ein CancellationToken benötigt, welcher dem Task übergeben wird. Mittels dieses Tokens kann ein vorzeitiger Abbruch initiiert und erkannt werden. Innerhalb des Tasks kann geprüft werden ob ein Abbruch erfolgen soll. In diesem Fall wird eine Funktion aufgerufen welche eine OperationCanceledException auslöst. Diese Exception kann ausserhalb des Tasks abgefangen werden um den vorzeitigen Abbruch zu erkennen. Der nachfolgende Quellcode zeigt ein entsprechendes Beispiel.

class Program
{
    static void Main(string[] args)
    {
        var cancellationSource = new CancellationTokenSource();
        CancellationToken token = cancellationSource.Token;

        Task task = Task.Factory.StartNew(() => DoWork(token), token);
        Thread.Sleep(TimeSpan.FromSeconds(1));

        cancellationSource.Cancel();

        try
        {
            task.Wait();
        }
        catch (AggregateException exception)
        {
            if (exception.InnerException is OperationCanceledException)
            {
                Console.Write("Task canceled!");
            }
        }


        Console.ReadKey();
    }

    static void DoWork(CancellationToken token)
    {
        Console.WriteLine("Hello World");
        Thread.Sleep(TimeSpan.FromSeconds(3));

        token.ThrowIfCancellationRequested();

        Console.WriteLine("Task finished");
    }
}

 
In den Zeilen 5 und 6 wird der Token erstellt und in Zeile 8 an den Task übergeben. In Zeile 11 wird der Abbruch des Tasks ausgelöst. Innerhalb des Tasks wird dieser Abbruch erkannt. Dazu dient der Befehl in Zeile 34. Die Hauptanwendung erkennt den Abbruch dadurch, dass die Wait Funktion zum Warten auf des Ende des Tasks innerhalb eines try-catch Blockes ausgeführt wird. Dadurch kann die angesprochene OperationCanceledException abgefangen werden. Bitte beachten Sie, dass diese Exception innerhalb einer AggregateException vorliegt.

 
Fazit

Die Task Klasse bieten Ihnen sehr einfach zu verwendende Funktionen um auf das Ende eines oder mehrere Tasks zu warten. Des Weiteren kann mittels CancellationToken ein vorzeitiger Abbruch eines Tasks ausgelöst werden.

Werbung
Dieser Beitrag wurde unter .NET, C#, Multithreading abgelegt und mit , , , , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Eine Antwort zu Multithreading in C#, Teil 22: Beenden von Tasks

  1. Lukas schreibt:

    Super Artikel, hat mir sehr geholfen! Kannst du vielleicht erklären was du genau mit Zeile 9 machst?
    LG
    Lukas

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s