98 votes

Quelle est la meilleure façon d'attraper une exception dans Task ?

Con System.Threading.Tasks.Task<TResult> je dois gérer les exceptions qui pourraient être levées. Je cherche la meilleure façon de le faire. Jusqu'à présent, j'ai créé une classe de base qui gère toutes les exceptions non attrapées dans l'appel de la fonction .ContinueWith(...)

Je me demande s'il n'y a pas un meilleur moyen de le faire. Ou même si c'est un bon moyen de le faire.

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
             */

        });        
    }
}

127voto

casperOne Points 49736

Vous pouvez le faire de deux façons, selon la version de la langue que vous utilisez.

C# 5.0 et plus

Vous pouvez utiliser le async y await des mots-clés pour vous simplifier une grande partie de ces tâches.

async y await ont été introduits dans le langage afin de simplifier l'utilisation de l'option Bibliothèque parallèle des tâches ce qui vous évite d'avoir à utiliser ContinueWith et vous permettant de continuer à programmer de manière descendante.

Pour cette raison, vous pouvez simplement utiliser un try / catch pour attraper l'exception, comme ceci :

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

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

Notez que la méthode encapsulant la méthode ci-dessus doit l'utilisation ont le async appliqué afin que vous puissiez utiliser await .

C# 4.0 et inférieur

Vous pouvez gérer les exceptions à l'aide de la fonction ContinueWith surcharge qui prend une valeur dans le TaskContinuationOptions énumération comme ça :

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

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

En OnlyOnFaulted membre de la TaskContinuationOptions L'énumération indique que la suite doit sólo être exécutée si la tâche antécédente a déclenché une exception.

Bien sûr, vous pouvez avoir plus d'un appel à ContinueWith à partir du même antécédent, en traitant le cas non exceptionnel :

// 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();

6voto

ZarathustrA Points 449

Vous pouvez créer une usine de tâches personnalisée, qui produira des tâches avec un traitement de gestion des exceptions intégré. Quelque chose comme ceci :

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

Vous pouvez oublier le traitement des exceptions pour les Tasks produites à partir de cette usine dans votre code client. Dans le même temps, vous pouvez toujours attendre la fin de ces Tasks ou les utiliser dans le style "Fire-and-Forget" :

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

Mais pour être honnête, je ne sais pas vraiment pourquoi vous voulez avoir un code de gestion de l'achèvement. En tout cas, cette décision dépend de la logique de votre application.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X