2016-04-28 2 views
4

は、次のコードを考えてみましょう:ContinueWith()を使用して元の例外を取得する方法?

using System; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 

namespace Demo 
{ 
    static class Program 
    { 
     static void Main() 
     { 
      var tasks = new Task[1]; 

      tasks[0] = Task.Run(() => throwExceptionAfterOneSecond()) 
       .ContinueWith(task => { 
        Console.WriteLine("ContinueWith()"); }, TaskContinuationOptions.NotOnFaulted); 

      try 
      { 
       Task.WaitAll(tasks); 
      } 

      catch (AggregateException ex) 
      { 
       Console.WriteLine("Exception received: " + ex.InnerExceptions.Single().Message); 
      } 
     } 

     static void throwExceptionAfterOneSecond() 
     { 
      Thread.Sleep(1000); 
      throw new InvalidOperationException("TEST"); 
     } 
    } 
} 

これは、次のような出力が得られます

Exception received: A task was canceled.

を私の質問は簡単です:どのように私は、元のInvalidOperationException("TEST");ではなくSystem.Threading.Tasks.TaskCanceledExceptionで入手できますか?

.ContinueWith()部分を削除すると、これは期待通りに機能し、その場合の出力はException received: TESTです。答えに

(また、この例では、純4.5を使用していますが、元のコードは、.NET 4.0を使用しなければならないことに注意してください)


SOLUTION

おかげで、これが今取り組んでいます。私は以下のソリューションを選択しました - 私は、元のタスクと継続タスクの両方で待機するために必要な:あなたは後でそれがException財産だ調べることができるようにTask.Run(() => throwExceptionAfterOneSecond())への参照を格納する必要が

using System; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 

namespace Demo 
{ 
    static class Program 
    { 
     static void Main() 
     { 
      var tasks = new Task[2]; 

      tasks[0] = Task.Run(() => throwExceptionAfterOneSecond()); 

      tasks[1] = tasks[0].ContinueWith(task => { 
       if (task.Status == TaskStatus.RanToCompletion) 
        Console.WriteLine("ContinueWith()"); }); 
      try 
      { 
       Task.WaitAll(tasks); 
      } 

      catch (AggregateException ex) 
      { 
       Console.WriteLine("Exception received: " + ex.InnerExceptions.Single().Message); 
      } 

      Console.WriteLine("Done."); 
     } 

     static void throwExceptionAfterOneSecond() 
     { 
      Thread.Sleep(1000); 
      throw new InvalidOperationException("TEST"); 
     } 
    } 
} 
+0

「TaskContinuationOptions.NotOnFaulted」を削除するにはどうすればよいですか? –

+0

@KirillShlenskiyこれを行うと、 'Task.WaitAll()'から何も例外が発生しません。 –

+1

内部タスクではなく、継続タスクを待っています。後で継続を追加してください: 'tasks [0] .ContinueWith(...)'。 –

答えて

2

。これは障害が発生した唯一のタスクです。他のタスクを調べても例外はありません。

私はTaskContinuationOptions.NotOnFaultedにも依存しません。これは、コントロールフローの例外を使用しているためです。例外がスローされることなく正常に完了していないタスクを待つのは難しいです。

.ContinueWith(task => { 
    if (task.Status == RanToCompletion) Console.WriteLine("ContinueWith()"); 
} 
+0

このアプローチは機能し、私の質問に最終的な解決策を追加します。 –

1

あなたは、彼らが成功事例の場合に例外の場合は独立した別々になるようにしてContinueWith一部を分割できます。ここでの例です:

var tasks = new Task[1]; 
tasks[0] = Task.Run(() => throwExceptionAfterOneSecond()); 

// For error handling. 
tasks[0].ContinueWith(task => 
    { 
     // Your logic to handle the exception goes here 

     // Aggregate exception 
     Console.WriteLine(task.Exception.Message); 

     // Inner exception, which is your custom exception 
     Console.WriteLine(task.Exception.InnerException.Message); 
    }, 
    TaskContinuationOptions.OnlyOnFaulted); 

// If it succeeded. 
tasks[0].ContinueWith(task => 
{ 
    // success 
    Console.WriteLine("ContinueWith()"); 
},TaskContinuationOptions.OnlyOnRanToCompletion); 
2

あなたが続ける内部から再スロー可能性がありcatch文で例外をキャッチする必要がある場合。例えば。

tasks[0] = Task.Run(() => throwExceptionAfterOneSecond()) 
    .ContinueWith(task => 
    { 
     if (task.IsFaulted) 
     { 
      // Throw the inner exception 
      throw task.Exception.InnerException; 
     } 

     Console.WriteLine("ContinueWith()"); 
    }); 
+0

これは、共通の再現防止パターンです。スタック情報を失い、複数の例外が発生した場合でも動作しません。 – usr

+0

はい、少し速くて汚いですが、必要なのは、 'TaskCanceledException'ではなく、元の' InvalidOperationException'をキャッチすることだけでした。追加のロジックを使用して、複数の例外をチェックしたり、内部例外としてラップして、innerExceptionなどのスタックトレースを保持することができます。 – Fermin

関連する問題