en / de
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

C# Concurrency Teil 10: Delegate Task Exceptions

Ein delegate Task repräsentiert eine CPU-bound Aufgabe die durch den Task Scheduler einem Thread zugewiesen wird. In Teil 8 wurde gezeigt wie man einen delegate Task mit der TPL erstellt und in Teil 9 wurde das korrekte Abbrechen einer Task behandelt. Dieser Teil zeigt, wie man Task Exceptions richtig verarbeitet und wie ein Task richtig wartet und Fortschritt melden kann.

Ausnahmen werden gespeichert im Task Objekt

Auftretende Exceptions werden in der Exception Eigenschaft vom Task Objekt gespeichert. Weil Tasks verschachtelt sein können, wird dazu der Typ AggregateException verwendet.

Es gibt im AggregateException Objekt zwei Eigenschaften:

Bei der TPL muss man immer die AggregateException abfangen und die InnerExceptions Sammlung auswerten. Diese sind je nach Hierarchie ebenfalls verschachtelt. Um die Hierarchie flach zu machen, gibt es den Aufruf ‚Flatten‘.

List<Exception> exceptions = aggregateException.Flatten().InnerExceptions.Where(e => !(e is OperationCanceledException)).ToList();

Wie beim Result kommt man erst an die Exceptions ran, wenn der Task fertig ist. Siehe den Abschnitt „Rückgabewerte“ vom Teil 8.

Unobserved Task Exceptions

Wenn der Benutzer vergisst eine Task Ausnahme abzufangen gehen diese Ausnahmen normalerweise verloren. Es gibt aber noch eine letzte Chance, diese Ausnahmen abzufangen und zu verarbeiten mit dem UnobserverdTaskException Event. Das Prinzip ist ähnlich wie beim UnhandledException Event der AppDomain oder Dispatcher Klasse. Der Event wird erst ausgelöst, wenn der Garbage Collecter das Task-Objekt aufräumt und feststellt, dass die Exception nicht verarbeitet wurde.

public MainWindow()
{
    _cancellationToken = _cancellationTokenSource.Token;
    InitializeComponent();
    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.UnhandledException += AppDomain_UnhandledException;
    Application.Current.DispatcherUnhandledException += Application_DispatcherUnhandledException; 
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}

private void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs dispatcherUnhandledExceptionEventArgs)
{
    Debug.WriteLine($"Unhandled exception caught on UI Thread {dispatcherUnhandledExceptionEventArgs.Exception}");
}

void AppDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    Debug.WriteLine($"Unhandled exception caught of any thread in AppDomain {e.ExceptionObject}");
}

void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    Debug.WriteLine($"Unhandled exception found in disposed Task oject {e.Exception}");
}

Als Alternative kann man in der Konfigurationssektion Folgendes einfügen:

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled=”true”/> 
    </runtime> 
</configuration>

Mit diesem Setting wird das Programm beendet wenn eine Unobserved Exception durch den Garbage Collector entdeckt wird.

Warten

Statt Thread.Sleep() soll man Task.Delay(milliseconds, CancellationToken).Wait(CancellationToken) verwenden. Dieser bricht das Warten ab wenn das CancellationToken aktiviert wurde und wirft eine OperationCanceledException.

Statt Thread.Sleep die Methode Task.Delay().Wait() mit 2x CancellationToken verwenden.

Beispiel: Task.Delay(1000, _cancellationToken).Wait(_cancellationToken);

Als Alternative kann man WaitHandle.WaitOne() vom Token verwenden. Dieser bricht das Warten ab wenn das CancellationToken aktiviert wurde, wirft aber keine Exception. Mann muss dann immer noch selber die OperationCanceledException werfen.

Progress

Wenn eine asynchrone Methode Vorschritt melden möchte, kann beim Aufruf eine Implementation von IProgress<T> übergeben werden. Die Schnittstelle hat nur eine Methode: Progress<T>() welcher auf dem Kontext ausgeführt wird worin das Progress Objekt erstellt wurde. Man muss den Aufruf also nicht remarshallen zum UI Thread.

Als alternative kann man auch den Eventhandler ProgressChanged benutzen um den Vorschritt zu melden.

private async void BtnStartTaskRunProgress_Click(object sender, RoutedEventArgs e)
{
    _progress.Foreground = new SolidColorBrush(Color.FromRgb(0, 0, 255));
    // The Progress<T> constructor captures the  UI context so the lambda will be run on the UI thread.
    var progress = new Progress<int>(percent =>
    {
        _progress.Value = percent;
    });
    try
    {
        // DoProcessing is run on the thread pool.
        await Task.Run(() => DoProcessing(_cancellationToken, progress), _cancellationToken);
    }
    catch (OperationCanceledException)
    {
        _progress.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));
        return;
    }
    _progress.Value = 100;
    _progress.Foreground = new SolidColorBrush(Color.FromRgb(0,255,0));
}

public void DoProcessing(CancellationToken cancellationToken, IProgress<int> progress)
{
    for (int i = 0; i < 100; ++i)
    {
        cancellationToken.ThrowIfCancellationRequested();
        // Waiting with cancellation support 
        Task.Delay(100, cancellationToken).Wait(cancellationToken);
        progress?.Report(i);
    }
}

Follow up

Im nächsten Blog werden promise Tasks für IO-bound Operationen erklärt zusammen mit den Statements async\await.

← Vorige Post
Nächster Post →
Kommentare

Eine Antwort zu “C# Concurrency Teil 10: Delegate Task Exceptions”

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden
NACH OBEN
Zur Webcast Übersicht