¿Cuál es la mejor manera de detectar una excepción en Task?

82

Con System.Threading.Tasks.Task<TResult>, tengo que gestionar las excepciones que podrían lanzarse. Estoy buscando la mejor manera de hacerlo. Hasta ahora, he creado una clase base que gestiona todas las excepciones no detectadas dentro de la llamada de.ContinueWith(...)

Me pregunto si hay una mejor manera de hacerlo. O incluso si es una buena forma de hacerlo.

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é
fuente

Respuestas:

107

Hay dos formas de hacer esto, dependiendo de la versión del idioma que esté usando.

C # 5.0 y superior

Puede utilizar las palabras clave asyncy awaitpara simplificarle mucho esto.

asyncy awaitse introdujeron en el lenguaje para simplificar el uso de Task Parallel Library , evitando que tenga que usar ContinueWithy permitiéndole continuar programando de forma descendente.

Debido a esto, simplemente puede usar un bloque try/catch para detectar la excepción, así:

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

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

Tenga en cuenta que el método que encapsula lo anterior debe tener asyncaplicada la palabra clave para que pueda usar await.

C # 4.0 y menos

Puede manejar excepciones usando la ContinueWithsobrecarga que toma un valor de la TaskContinuationOptionsenumeración , así:

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

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

El OnlyOnFaultedmiembro de la TaskContinuationOptionsenumeración indica que la continuación solo debe ejecutarse si la tarea anterior arrojó una excepción.

Por supuesto, puede tener más de una llamada para ContinueWithel mismo antecedente, manejando el caso no excepcional:

// 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
fuente
1
¿Cómo sabría el tipo de excepción en el método anónimo? Si lo hago t.Exception, intellisense no expondrá las propiedades innerexception, message ... etc ...
guiomie
4
@guiomie t es la excepción.
casperOne
2
el contexto no está definido ¿qué es?
MonsterMMORPG
@MonsterMMORPG SynchronizationContext, si es necesario.
casperOne
Ty como respuesta, ¿por qué lo necesitaríamos? Quiero decir, ¿en qué caso? es suficiente ? myTask.ContinueWith (t => ErrorLogger.LogError ("Se produjo un error en func_CheckWaitingToProcessPages y se inició la tarea:" + t), TaskContinuationOptions.OnlyOnFaulted);
MonsterMMORPG
5

Puede crear una fábrica de tareas personalizada, que producirá tareas con procesamiento de manejo de excepciones integrado. Algo como esto:

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

Puede olvidarse del procesamiento de excepciones para Tareas producidas desde esta fábrica en su código de cliente. Al mismo tiempo, aún puede esperar la finalización de dichas tareas o usarlas en el estilo 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

Pero para ser honesto, no estoy muy seguro de por qué desea tener un código de manejo de finalización. En cualquier caso, esta decisión depende de la lógica de su aplicación.

ZarathustrA
fuente