Каков наилучший способ поймать исключение в задаче?


С System.Threading.Tasks.Task<TResult>, Я должен управлять исключениями, которые могут быть брошены. Я ищу лучший способ сделать это. До сих пор я создал базовый класс, который управляет всеми неперехваченными исключениями внутри вызова .ContinueWith(...)

мне интересно, есть ли лучший способ сделать это. Или даже если это хороший способ сделать это.

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

        });        
    }
}
2 58

2 ответа:

есть два способа сделать это, в зависимости от версии языка, который вы используете.

в C# 5.0 и выше

вы можете использовать async и await ключевые слова, чтобы упростить многое из этого для вас.

async и await были введены в язык, чтобы упростить использование Параллельных Задач Библиотека, предотвращая вас от использования ContinueWith и что вы продолжать программировать в нисходящем порядке.

из-за этого, вы можете просто использовать try/catch блок, чтобы поймать исключение, вот так:

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

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

обратите внимание, что метод инкапсуляции выше должны использовать есть async ключевое слово применяется, так что вы можете использовать await.

в C# 4.0 и ниже

вы можете обрабатывать исключения с помощью ContinueWith перегрузка это принимает значение от TaskContinuationOptions перечисление, например:

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

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

The OnlyOnFaulted член TaskContinuationOptions перечисление указывает, что продолжение должно только выполняется, если предшествующая задача вызвала исключение.

конечно, вы можете иметь более одного вызова ContinueWith от того же антецедента, обрабатывая не исключительный случай:

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

вы можете создать некоторую пользовательскую фабрику задач, которая будет производить задачи с встроенной обработкой обработки исключений. Что-то вроде этого:

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

вы можете забыть об обработке исключений для задач, созданных с этой фабрики в вашем клиентском коде. В то же время вы все еще можете дождаться завершения таких задач или использовать их в стиле 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

но если честно, я не совсем уверен, почему вы хотите иметь код обработки завершения. В любом случае это решение зависит от логики вашего приложения.