Jaki jest najlepszy sposób na wyłapanie wyjątku w zadaniu?

83

W przypadku System.Threading.Tasks.Task<TResult>muszę zarządzać wyjątkami, które mogą zostać wyrzucone. Szukam najlepszego sposobu, żeby to zrobić. Do tej pory utworzyłem klasę bazową, która zarządza wszystkimi nieprzechwyconymi wyjątkami wewnątrz wywołania.ContinueWith(...)

Zastanawiam się, czy jest na to lepszy sposób. Lub nawet jeśli to dobry sposób na zrobienie tego.

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é
źródło

Odpowiedzi:

109

Możesz to zrobić na dwa sposoby, w zależności od wersji języka, którego używasz.

C # 5.0 i nowsze

Możesz użyć słów kluczowych asynci, awaitaby uprościć wiele dla siebie.

asynci awaitzostały wprowadzone do języka w celu uproszczenia korzystania z biblioteki zadań równoległych , co zapobiega konieczności używania ContinueWithi umożliwia dalsze programowanie w sposób odgórny.

Z tego powodu możesz po prostu użyć try/catch block, aby złapać wyjątek, na przykład:

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

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

Zauważ, że metoda hermetyzująca powyższe musi mieć asynczastosowane słowo kluczowe, abyś mógł użyć await.

C # 4.0 i starsze

Możesz obsługiwać wyjątki przy użyciu ContinueWithprzeciążenia, które pobiera wartość z TaskContinuationOptionswyliczenia , na przykład:

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

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

Element OnlyOnFaultedczłonkowski TaskContinuationOptionswyliczenia wskazuje, że kontynuacja powinna zostać wykonana tylko wtedy, gdy zadanie poprzedzające zgłosiło wyjątek.

Oczywiście możesz mieć więcej niż jedno wywołanie ContinueWithtego samego poprzednika, obsługując przypadek inny niż wyjątkowy:

// 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
źródło
1
Skąd znasz typ wyjątku w metodzie anonimowej? Jeśli zrobię t.Exception, intellisense nie ujawni właściwości innychrexception, message ... etc ...
guiomie
4
@guiomie t jest wyjątkiem.
casperOne
2
kontekst nie jest zdefiniowany, co to jest?
MonsterMMORPG
@MonsterMMORPG SynchronizationContext, w razie potrzeby.
casperOne
Ty na odpowiedź, dlaczego byśmy tego potrzebowali? Mam na myśli w jakim przypadku? czy to wystarczy ? myTask.ContinueWith (t => ErrorLogger.LogError ("Wystąpił błąd w func_CheckWaitingToProcessPages uruchomiono zadanie i błąd:" + t), TaskContinuationOptions.OnlyOnFaulted);
MonsterMMORPG
5

Możesz utworzyć niestandardową fabrykę zadań, która będzie generować zadania z osadzonym przetwarzaniem obsługi wyjątków. Coś takiego:

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

Możesz zapomnieć o przetwarzaniu wyjątków dla zadań utworzonych z tej fabryki w kodzie klienta. Jednocześnie możesz jeszcze poczekać na ukończenie takich Zadań lub korzystać z nich w stylu odpal i zapomnij:

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

Ale szczerze mówiąc, nie jestem pewien, dlaczego chcesz mieć kod obsługujący uzupełnianie. W każdym razie ta decyzja zależy od logiki twojej aplikacji.

ZarathustrA
źródło