2014-03-13 151 views
16

此代碼會引發異常。是否有可能定義一個應用程序全局處理程序,將抓住它?TAP全局異常處理程序

string x = await DoSomethingAsync(); 

使用.NET 4.5/WPF

+4

try/catch圍繞該聲明出現了什麼問題? –

+5

提示創建Applicaton.DispatcherUnhandledException的相同事物。 – Sam

+0

那麼這是爲可能發生的異常而創建的,您無法捕捉。一般來說,您應該捕獲代碼中可能出現的異常。但是,這裏有一些鏈接可能會幫助你相處:http://stackoverflow.com/questions/14167746/appdomain-unhandledexception&http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception% 28v = vs.110%29.aspx –

回答

18

這實際上是一個問題,如果我理解正確。我最初投票決定關閉它,但現在撤回了我的投票。

瞭解如何將在async Task方法內拋出的異常傳播到其外部很重要。最重要的是,這種異常需要通過處理完成任務的代碼觀察到。

例如,下面是一個簡單的WPF應用程序,我在NET 4.5.1:

using System; 
using System.Threading.Tasks; 
using System.Windows; 

namespace WpfApplication_22369179 
{ 
    public partial class MainWindow : Window 
    { 
     Task _task; 

     public MainWindow() 
     { 
      InitializeComponent(); 

      AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 
      TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; 

      _task = DoAsync(); 
     } 

     async Task DoAsync() 
     { 
      await Task.Delay(1000); 

      MessageBox.Show("Before throwing..."); 

      GCAsync(); // fire-and-forget the GC 

      throw new ApplicationException("Surprise"); 
     } 

     async void GCAsync() 
     { 
      await Task.Delay(1000); 

      MessageBox.Show("Before GC..."); 

      // garbage-collect the task without observing its exception 
      _task = null; 
      GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); 
     } 

     void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) 
     { 
      MessageBox.Show("TaskScheduler_UnobservedTaskException:" + e.Exception.Message); 
     } 

     void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 
     { 
      MessageBox.Show("CurrentDomain_UnhandledException:" + ((Exception)e.ExceptionObject).Message); 
     } 
    } 
} 

一旦ApplicationException已經扔,這是不言而喻沒有觀察到。 TaskScheduler_UnobservedTaskExceptionCurrentDomain_UnhandledException都不會被調用。在等待或等待_task對象之前,異常處於休眠狀態。在上面的例子中,它從來沒有被觀察到,因此TaskScheduler_UnobservedTaskException只有在任務被垃圾回收時纔會被調用。。然後這個異常將被吞入

老.NET 4.0的行爲,其中AppDomain.CurrentDomain.UnhandledException事件被炒魷魚和應用程序崩潰,可以通過配置ThrowUnobservedTaskExceptionsapp.config啓用:

<configuration> 
    <runtime> 
     <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime> 
</configuration> 

當啓用此方式,AppDomain.CurrentDomain.UnhandledException仍將被解僱後TaskScheduler.UnobservedTaskException當異常得到垃圾回收,而不是它拋出的地方。

此行爲由Stephen Toub在其"Task Exception Handling in .NET 4.5"博客文章中描述。關於任務垃圾收集的部分在帖子的評論中描述。

這就是async Task方法的情況。這個故事與async void方法完全不同,它們通常用於事件處理程序。讓我們改變了這樣的代碼:

public MainWindow() 
{ 
    InitializeComponent(); 

    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; 

    this.Loaded += MainWindow_Loaded; 
} 

async void MainWindow_Loaded(object sender, RoutedEventArgs e) 
{ 
    await Task.Delay(1000); 

    MessageBox.Show("Before throwing..."); 

    throw new ApplicationException("Surprise"); 
} 

因爲它是async void沒有Task參考扶住(所以沒有什麼可以觀察到可能或垃圾收集後)。在這種情況下,立即在當前同步上下文中引發異常。對於WPF應用程序,Dispatcher.UnhandledException將先被觸發,然後是Application.Current.DispatcherUnhandledException,然後AppDomain.CurrentDomain.UnhandledException。最後,如果沒有處理這些事件(EventArgs.Handled未設置爲true),則無論ThrowUnobservedTaskExceptions設置如何,該應用都會崩潰。 TaskScheduler.UnobservedTaskException不是在這種情況下被解僱,出於同樣的原因:沒有Task

+1

Noseratio你在理解我的問題上很有用。但是,在GC運行之前不會引發UnobservedTaskException,這使得它在功能上無用。 Toub在其帖子的問題部分提供了IgnoreExceptions解決方案。我不明白爲什麼編譯器不會在創建異步方法包裝器時添加它。如果事件得到處理,則在繼續內部應該引發UnobservedTaskExpception。 – Sam

+1

@Sam,IMO這種行爲非常有道理。 「任務」對象是一個承諾,是一個延遲的結果。剛剛完成的事實並不意味着它必須馬上被觀察到。由於與其他任務組合(例如'WhenAll')或其他異步邏輯特性,可能會在10分鐘內觀察到它。爲什麼編譯器會對此做出任何假設並打破這種邏輯?它不應該。 – Noseratio

+0

您可以完全控制這一點。可以用'try/catch'來處理異常,或者使用類似前面提到的'IgnoreException'來標記爲觀察到的異常。 – Noseratio

1

async代碼,你可以通過註冊TaskScheduler.UnobservedTaskException事件的處理程序處理沒有​​觀察到異常EDITED按@ Noseration的評論

在.NET 4.5。如果您不訪問Task.Result,Task.Exception屬性,並且您沒有撥打Task.Wait,則認爲例外情況未被發現。

在未觀察到異常到達TaskScheduler.UnobservedTaskException事件處理程序後,默認行爲是吞下此異常,以免程序崩潰。這種行爲可以在配置文件中加入更改如下:

<configuration> 
    <runtime> 
     <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime> 
</configuration> 
+0

是的,但在這種情況下,他正在等待結果,因此該任務將被視爲已觀察到。 –

+0

這個**對於.NET 4.0是**,但對於.NET 4.5+不再適用。查看[我的答案](http://stackoverflow.com/a/22395161/1768303)瞭解詳情。 – Noseratio

0

那麼,你將如何定義一個應用程序的全局處理程序來處理這種情況的一個例外?

string x = DoSomething(); 

機會是你的問題的答案是完全一樣的。看起來您正在等待一個異步方法,並且編譯器會竭盡全力確保異步方法中發生的任何異常都以一種可讓您像在同步代碼中處理它的方式傳播和解除。這是異步/等待的主要好處之一。

1

將事件綁定到AppDomain.CurrentDomain.FirstChanceException將保證您的異常將被捕獲。正如@Noseratio指出的那樣,即使在catch塊中正常處理異常並且應用程序繼續運行,您也會收到應用程序中每個異常的通知。

但是,我仍然看到這個事件對於至少捕獲應用程序暫停之前拋出的最後一些異常或者其他一些調試場景很有用。

如果你想保護自己免受這種

string x = await DoSomethingAsync(); 

我給你的建議是,不這樣做,將一個try catch塊:-)

1

您可以隨時做以下操作使用Application.DispatcherUnhandledException方法處理異常。當然,它會在TargetInvocationException之內給你,可能不如其他方法漂亮。但它工作得很好

_executeTask = executeMethod(parameter); 
_executeTask.ContinueWith(x => 
{ 
    Dispatcher.CurrentDispatcher.Invoke(new Action<Task>((task) => 
    { 
     if (task.Exception != null) 
      throw task.Exception.Flatten().InnerException; 
    }), x); 
}, TaskContinuationOptions.OnlyOnFaulted);