web-dev-qa-db-ja.com

Taskで例外をキャッチする最良の方法は何ですか?

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

        });        
    }
}
_
68
JiBéDoublevé

これを行うには、使用している言語のバージョンに応じて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.
}

上記のmustをカプセル化するメソッドには、asyncキーワードが適用されているため、awaitを使用できます。

C#4.0以前

ContinueWith overload を使用して例外を処理できます。これは TaskContinuationOptions enumeration から値を取得します。

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

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

OnlyOnFaulted列挙体のTaskContinuationOptionsメンバーは、先行タスクが例外をスローした場合に、継続onlyを実行する必要があることを示します。

もちろん、同じ前件から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();
92
casperOne

例外処理処理が埋め込まれたタスクを生成するカスタムタスクファクトリを作成できます。このようなもの:

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

しかし、正直に言うと、完了処理コードが必要な理由がよくわかりません。いずれにしても、この決定はアプリケーションのロジックに依存します。

5
ZarathustrA