web-dev-qa-db-ja.com

タスクの例外を適切に処理する方法

次のコードをご覧ください

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);
    Console.ReadKey();
    Console.WriteLine("Hello");
}

private static int div(int x, int y)
{
    if (y == 0)
    {
        throw new ArgumentException("y");
    }
    return x / y;
}

リリースモードでコードを実行すると、「1つ以上のエラーが発生しました」という出力が表示され、「Enter」キーを押すと「Hello」も表示されます。デバッグモードでコードを実行すると、出力は次のようになります。リリースモード。ただし、IDEでデバッグする場合、コントロールが行を実行するとIDE例外メッセージ(「ユーザーコードで未処理の例外」)が表示されます。

throw new ArgumentException("y"); 

そこから続行すると、プログラムはクラッシュせず、リリースモードと同じ出力が表示されます。これは例外を処理する適切な方法ですか?

51
Anirban Paul

おそらく、別個のOnlyOnFaultedハンドラーとOnlyOnRanToCompletionハンドラーは必要なく、OnlyOnCanceledを処理していません。詳細については this answer を確認してください。

ただし、IDEでデバッグする場合、コントロールが行を実行すると、IDE例外メッセージ(「ユーザーコードの未処理の例外」)が表示されます

おそらくデバッガー/例外オプションで有効にしているため、デバッガーの下に例外が表示されます(Ctrl+Alt+E)。

そこから続行すると、プログラムはクラッシュせず、リリースモードと同じ出力が表示されます。これは例外を処理する適切な方法ですか?

スローされたがTaskアクション内で処理されなかった例外は、自動的に再スローされません。代わりに、将来の観測のためにTask.ExceptionAggregateException型)としてラップされます。元の例外にはException.InnerExceptionとしてアクセスできます:

Exception ex = task.Exception;
if (ex != null && ex.InnerException != null)
    ex = ex.InnerException;

この場合にプログラムをクラッシュさせるには、タスクアクションの例外outsideを実際に観察する必要があります。 Task.Resultを参照することにより:

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);

    Console.ReadKey();

    Console.WriteLine("result: " + task.Result); // will crash here

    // you can also check task.Exception

    Console.WriteLine("Hello");
}

詳細: タスクおよび未処理の例外。NET 4.5でのタスク例外処理

コメントに対処するために更新されました:.NET 4.0およびVS2010を使用したUIアプリでこれを行う方法は次のとおりです:

void Button_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => 
    {
        return div(32, 0); 
    }).ContinueWith((t) =>
    {
        if (t.IsFaulted)
        {
            // faulted with exception
            Exception ex = t.Exception;
            while (ex is AggregateException && ex.InnerException != null)
                ex = ex.InnerException;
            MessageBox.Show("Error: " + ex.Message);
        }
        else if (t.IsCanceled)
        {
            // this should not happen 
            // as you don't pass a CancellationToken into your task
            MessageBox.Show("Canclled.");
        }
        else
        {
            // completed successfully
            MessageBox.Show("Result: " + t.Result);
        }
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

.NET 4.0をターゲットにし、観察されない例外(つまり、タスクがガベージコレクションされたときに再スロー)の.NET 4.0の動作が必要な限り、explicitlyapp.configで設定します:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <runtime>
    <ThrowUnobservedTaskExceptions enabled="true"/>
  </runtime>
</configuration>

詳細については、これを確認してください。

。NET4で観察されないタスク例外

63
noseratio

あなたが持っているものはAggregateExceptionです。これはタスクからスローされ、特定の例外を見つけるために内部例外を確認する必要があります。このような:

task.ContinueWith(t =>
{
    if (t.Exception is AggregateException) // is it an AggregateException?
    {
        var ae = t.Exception as AggregateException;

        foreach (var e in ae.InnerExceptions) // loop them and print their messages
        {
            Console.WriteLine(e.Message); // output is "y" .. because that's what you threw
        }
    }
},
TaskContinuationOptions.OnlyOnFaulted);
4
Simon Whitehead

.Net 4.5以降、AggregateException.GetBaseException()を使用して「この例外の根本原因」を返すことができます。

https://docs.Microsoft.com/en-us/dotnet/api/system.aggregateexception.getbaseexception?view=netframework-4.7.2

しかし、ドキュメントは少しずれているようです。別のAggregateExceptionを返すと主張します。ただし、スローされたArgumentExceptionを返すことがわかると思います。

3
Cody Barnes

「1つまたは複数のエラーが発生しました」は、タスクプールによって作成されたラッパー例外に由来します。必要な場合は、Console.WriteLine(t.Exception.ToString())を使用して例外全体を出力します。

IDEは、処理されたかどうかに関係なく、すべての例外を自動的にキャプチャします。

0
hsun324

タスクを使用しているため、実行中に発生したすべての例外をラップする AggregateException を取得する必要があります。 AggregateException.ToString()メソッドのデフォルト出力であるため、One or more errors occurredメッセージが表示されます。

例外のインスタンスの Handle メソッドが必要です。

また、このような例外を処理する適切なアプローチ here も参照してください。

0
Tony