2016-04-28 54 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

(另请注意,这个例子是使用.NET 4.5,但原代码必须使用.NET 4.0)


SOLUTION

感谢的答案,这是现在的工作。我选择了以下解决方案 - 我需要等待双方的原始任务和后续任务:

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

你需要参考存储Task.Run(() => throwExceptionAfterOneSecond())这样以后可以检查它的Exception财产。这是错误的唯一任务。检查任何其他任务不会提供该例外。

我也不会依赖于TaskContinuationOptions.NotOnFaulted因为这非常强制使用控制流的异常。如果没有抛出异常,很难等待非正常完成的任务。

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

这种方法很有效,我会为我的问题添加最终解决方案。 –

1

你可以在分裂的ContinueWith部分这样一种方式,他们在例外的情况下,单独的和独立的sucess的情况。这里有一个例子:

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

是的,它有点'快而肮脏',但如果所需的只是捕获原始的'InvalidOperationException'而不是'TaskCanceledException',那么它就允许它。可以使用其他逻辑来检查多个异常或将其作为内部异常进行包装,以便在innerException中保留堆栈跟踪等。 – Fermin

相关问题