2016-04-28 10 views
4

Betrachten Sie den folgenden Code ein:Wie bekomme ich die ursprüngliche Ausnahme, wenn ich ContinueWith() verwende?

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

Dies ergibt die folgende Ausgabe:

Exception received: A task was canceled.

Meine Frage ist einfach: Wie komme ich an der ursprünglichen InvalidOperationException("TEST"); eher als ein System.Threading.Tasks.TaskCanceledException?

Beachten Sie, dass wenn Sie den .ContinueWith() Teil entfernen, funktioniert dies wie erwartet und die Ausgabe in diesem Fall ist Exception received: TEST.

(Beachten Sie auch, dass in diesem Beispiel verwendet, ist .NET 4.5, aber die ursprüngliche Code muss .NET 4.0 verwenden)


SOLUTION

Dank der Antworten, das funktioniert jetzt. Ich entschied mich für die folgende Lösung - ich brauchte auf die ursprüngliche Aufgabe sowohl AND Aufgabe der Fortsetzung warten:

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

Wie wäre es, 'TaskContinuationOptions.NotOnFaulted' zu entfernen? –

+0

@KirillShlenskiy Wenn Sie das tun, erhalten Sie keine Ausnahme von 'Task.WaitAll()'. –

+1

Sie warten auf die Fortsetzung Aufgabe, anstatt die innere Aufgabe. Fügen Sie die Fortsetzung später hinzu: 'tasks [0] .ContinueWith (...)'. –

Antwort

2

Sie müssen einen Verweis auf Task.Run(() => throwExceptionAfterOneSecond()) speichern, damit Sie später untersuchen kann es Exception Eigenschaft. Dies ist die einzige Aufgabe, die fehlerhaft ist. Das Untersuchen einer anderen Aufgabe wird diese Ausnahme nicht bereitstellen.

Ich würde auch nicht auf TaskContinuationOptions.NotOnFaulted verlassen, weil dies ziemlich zwingt, Ausnahmen für den Kontrollfluss zu verwenden. Es ist schwierig, auf eine nicht normal abgeschlossene Aufgabe zu warten, ohne dass eine Ausnahme ausgelöst wird.

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

Dieser Ansatz funktioniert, ich füge die endgültige Lösung zu meiner Frage hinzu. –

1

Sie aufteilen könnte den ContinueWith Teil in einer solchen Art und Weise, dass sie im Falle einer Ausnahme und getrennt bei Sucess getrennt sind. hier ist ein Beispiel:

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

Wenn Sie die Ausnahme in der catch Aussage fangen müssen Sie aus dem Innern der weiterhin erneut auslösen könnten. Z.B.

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

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

Dies ist das häufig wiederkehrende Anti-Pattern. Verliert Stapelinformationen und funktioniert auch nicht bei mehreren Ausnahmen. – usr

+0

Ja, es ist ein bisschen "schnell und schmutzig", aber wenn es nur nötig war, die ursprüngliche 'InvalidOperationException' und nicht 'TaskCanceledException' zu fangen, dann erlaubt es das. Zusätzliche Logik könnte verwendet werden, um nach mehreren Ausnahmen zu suchen oder als innere Ausnahme umbrochen zu werden, um die Stapelverfolgung in innerException usw. zu erhalten. – Fermin