Was ist der beste Weg, um Ausnahmen in Task abzufangen?

82

Mit System.Threading.Tasks.Task<TResult>muss ich die Ausnahmen verwalten, die ausgelöst werden könnten. Ich suche nach dem besten Weg, das zu tun. Bisher habe ich eine Basisklasse erstellt, die alle nicht erfassten Ausnahmen innerhalb des Aufrufs von verwaltet.ContinueWith(...)

Ich frage mich, ob es einen besseren Weg gibt, das zu tun. Oder auch wenn es ein guter Weg ist, das zu tun.

public class BaseClass
{
    protected void ExecuteIfTaskIsNotFaulted<T>(Task<T> e, Action action)
    {
        if (!e.IsFaulted) { action(); }
        else
        {
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
            {
                /* I display a window explaining the error in the GUI 
                 * and I log the error.
                 */
                this.Handle.Error(e.Exception);
            }));            
        }
    }
}   

public class ChildClass : BaseClass
{
    public void DoItInAThread()
    {
        var context = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory.StartNew<StateObject>(() => this.Action())
                    .ContinueWith(e => this.ContinuedAction(e), context);
    }

    private void ContinuedAction(Task<StateObject> e)
    {
        this.ExecuteIfTaskIsNotFaulted(e, () =>
        {
            /* The action to execute 
             * I do stuff with e.Result
             */

        });        
    }
}
JiBéDoublevé
quelle

Antworten:

107

Es gibt zwei Möglichkeiten, dies zu tun, abhängig von der Version der Sprache, die Sie verwenden.

C # 5.0 und höher

Sie können die Schlüsselwörter asyncund verwenden await, um dies für Sie erheblich zu vereinfachen.

asyncund awaitwurden in die Sprache eingeführt, um die Verwendung der Task Parallel Library zu vereinfachen, ContinueWithsodass Sie sie nicht mehr verwenden müssen und weiterhin von oben nach unten programmieren können.

Aus diesem Grund können Sie einfach einen try/catch -Block verwenden, um die Ausnahme abzufangen:

try
{
    // Start the task.
    var task = Task.Factory.StartNew<StateObject>(() => { /* action */ });

    // Await the task.
    await task;
}
catch (Exception e)
{
    // Perform cleanup here.
}

Beachten Sie, dass das Verfahren die oben Einkapseln muss Verwendung haben das asyncSchlüsselwort angewendet , so dass Sie verwenden können await.

C # 4.0 und darunter

Sie können Ausnahmen mit der ContinueWithÜberladung behandeln , die einen Wert aus der TaskContinuationOptionsAufzählung übernimmt , wie folgt:

// Get the task.
var task = Task.Factory.StartNew<StateObject>(() => { /* action */ });

// For error handling.
task.ContinueWith(t => { /* error handling */ }, context,
    TaskContinuationOptions.OnlyOnFaulted);

Das OnlyOnFaultedMitglied der TaskContinuationOptionsAufzählung gibt an, dass die Fortsetzung nur ausgeführt werden soll, wenn die vorhergehende Aufgabe eine Ausnahme ausgelöst hat.

Natürlich können Sie mehr als einen Anruf haben, ContinueWithum denselben Vorgänger auszuschalten und den nicht außergewöhnlichen Fall zu behandeln:

// Get the task.
var task = new Task<StateObject>(() => { /* action */ });

// For error handling.
task.ContinueWith(t => { /* error handling */ }, context, 
    TaskContinuationOptions.OnlyOnFaulted);

// If it succeeded.
task.ContinueWith(t => { /* on success */ }, context,
    TaskContinuationOptions.OnlyOnRanToCompletion);

// Run task.
task.Start();
casperOne
quelle
1
Wie würden Sie den Ausnahmetyp in der anonymen Methode kennen? Wenn ich t.Exception mache, wird Intellisense die Eigenschaften der inneren Ausnahme, der Nachricht ... usw. nicht offenlegen ...
Guiomie
4
@guiomie t ist die Ausnahme.
CasperOne
2
Kontext ist nicht definiert, was ist es?
MonsterMMORPG
@MonsterMMORPG SynchronizationContext, falls erforderlich.
CasperOne
Ty für die Antwort, warum sollten wir es brauchen? Ich meine in welchem ​​Fall? ist das genug ? myTask.ContinueWith (t => ErrorLogger.LogError ("Fehler beim Starten von Task und Fehler bei func_CheckWaitingToProcessPages:" + t), TaskContinuationOptions.OnlyOnFaulted);
MonsterMMORPG
5

Sie können eine benutzerdefinierte Task-Factory erstellen, in der Aufgaben mit eingebetteter Ausnahmebehandlungsverarbeitung erstellt werden. Etwas wie das:

using System;
using System.Threading.Tasks;

class FaFTaskFactory
{
    public static Task StartNew(Action action)
    {
        return Task.Factory.StartNew(action).ContinueWith(
            c =>
            {
                AggregateException exception = c.Exception;

                // Your Exception Handling Code
            },
            TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously
        ).ContinueWith(
            c =>
            {
                // Your task accomplishing Code
            },
            TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously
        );
    }

    public static Task StartNew(Action action, Action<Task> exception_handler, Action<Task> completion_handler)
    {
        return Task.Factory.StartNew(action).ContinueWith(
            exception_handler,
            TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously
        ).ContinueWith(
            completion_handler,
            TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously
        );
    }
};

Sie können die Verarbeitung von Ausnahmen für Aufgaben, die in dieser Factory erstellt wurden, in Ihrem Client-Code vergessen. Gleichzeitig können Sie noch warten, bis solche Aufgaben erledigt sind, oder sie im Fire-and-Forget-Stil verwenden:

var task1 = FaFTaskFactory.StartNew( () => { throw new NullReferenceException(); } );
var task2 = FaFTaskFactory.StartNew( () => { throw new NullReferenceException(); },
                                      c => {    Console.WriteLine("Exception!"); },
                                      c => {    Console.WriteLine("Success!"  ); } );

task1.Wait(); // You can omit this
task2.Wait(); // You can omit this

Aber wenn Sie ehrlich sind, bin ich mir nicht sicher, warum Sie einen Code für die Vervollständigung haben möchten. In jedem Fall hängt diese Entscheidung von der Logik Ihrer Anwendung ab.

ZarathustrA
quelle