2011-08-31 53 views
1

我需要在我的應用程序中知道什麼時候動態執行的底層組件會拋出進程損壞的狀態異常,以便我可以記錄它並將該組件標記爲不適合再次加載並使我的過程崩潰。使用Async.Catch處理進程損壞的狀態異常

該組件的執行是異步執行的,我使用Async.Catch來處理該異常。我嘗試了下面的代碼來測試Async.Catch的行爲,在我看來,Async.Catch是掛起來的。這對我來說是一種不良影響,我懷疑所有PCSE都會導致相同的行爲。

任何人都知道如何擺脫這種情況?

let a = async { 
    System.Threading.Thread.CurrentThread.Abort() 
} 

let b = async { 
    let! m = Async.Catch a 
    return match m with 
      | Choice1Of2 p -> "hello" 
      | Choice2Of2 e -> "Caught: " + e.ToString() 
} 

Async.RunSynchronously b;; 

編輯1:我發現,指着我使用或者與SecurityCriticalAttributeHandleProcessCorruptedStateExceptionsAttribute在一起,或使用一個配置項legacyCorruptedState­­ExceptionsPolicy=true的文檔。如果可能的話,我不想使用配置條目。

編輯2:基於在註釋的建議,我修改了出租以「B」的結合如下:

let b = async { 
     try 
      let! m = Async.Catch a 
      return "hello" 
     with 
      | :? System.Threading.ThreadAbortException as e -> return "Caught: " + e.ToString() 
    } 

該程序仍然掛起而不返回或投擲。

+0

不回答你的問題,但我懷疑,你可以使用'嘗試/的with'代替'Async.Catch',使您的異步塊更具可讀性。 – kvb

+0

修改了代碼(請參閱EDIT2),仍然是相同的行爲 –

+2

我不指望它解決問題,但我的意思是讓你做'讓! m =返回「hello」'而不是將'Async.Catch'移動到'try'塊中。 – kvb

回答

5

這是一個棘手的問題 - 在正常的函數中,你可以捕獲ThreadAbortException並做出反應,但是你不能真正處理它,因爲它會自動重新拋出(並最終會終止線程)。

在F#異步工作流中,處理異常並且F#異步運行時存儲它以便它可以通過延續來報告它,但是在它有機會執行此操作之前,.NET會重新拋出異常,並且它會殺死線程(因此RunSynchronously掛起)。

問題是 - 報告異常,F#異步需要進行一些調用。該調用不能在當前線程(正在取消)上進行。如果您希望發生異常,您可以在線程池中開始工作並自行處理。 (F#不能自動執行,因爲這會造成太多開銷)。

您可以使用下面的幫助:

type Microsoft.FSharp.Control.Async with 
    static member CatchAbort<'R>(f : unit -> 'R) : Async<'R> = 
    async { let hndl = new AutoResetEvent(false) 
      let result = ref (Choice3Of3()) 
      ThreadPool.QueueUserWorkItem(fun _ -> 
       try 
       result := Choice1Of3 (f()) 
       hndl.Set() |> ignore 
       with 
       | e -> 
        // This handler runs correctly even for ThreadAbort 
        result := Choice2Of3 e 
        hndl.Set() |> ignore) |> ignore 
      let! _ = Async.AwaitWaitHandle(hndl) 
      match !result with 
      | Choice1Of3(res) -> return res 
      | Choice2Of3(exn) -> 
       // Wrap or rethrow the exception in some way 
       return raise exn 
      | Choice3Of3 _ -> return failwith "unexpected case" } 

這將啓動指定功能(這是不是異步)的線程池線程。函數完成或拋出後,它會將結果報告回原始線程,以恢復工作流程。

適應你的榜樣,這應該像預期的那樣:

let a = async { 
    let! value = Async.CatchAbort(fun() -> 
     System.Threading.Thread.CurrentThread.Abort() 
     "not aborted") 
    return value } 

let b = async { 
    try let! m = a 
     printfn "ok" 
    with e -> printfn "Caught: %A" e } 
+0

感謝您的一個很好的答案。我的主要要求是處理異步函數內發生的所有進程損壞的狀態異常。所以我仍然會遇到異步工作流的問題,因爲你在回答中提到的原因,不會報告回調中發生的異常。順便說一句,我只是使用線程中止作爲例子。現在看起來,我可能必須從一個懸而未決的情況中恢復的唯一方法是設置一個計時器並報告我的異步工作流程掛起了一個未知的原因,然後使進程崩潰。公平? –

+0

我不確定其他PCSE異常如何表現,但我認爲它們以相似的方式終止線程,所以它不應該有所作爲。你提到你正在調用一個外部組件 - 我假設組件不是用F#編寫的,並且不使用異步工作流程,所以你應該可以像使用我的例子一樣使用'CatchAbort'將它包裝在一個異步中。 –

+0

沒有這個額外的包裝,你將無法捕捉到異常,因爲F#異步工作流嘗試在內部捕獲它並報告它(如果線程終止,這是不可能的)。 –

3

你可以閱讀here - ThreadAbortException是CLR中的那些特殊異常之一。它似乎很難打破Asycn-Pattern,所以我想這就是問題所在。

嘗試與另一個例外,看看是否會工作(它應該)。

+0

該文檔指出,終止塊將在線程終止之前運行,我期望異步工作流能夠優雅地處理。而且,困擾我的是這個過程甚至不會崩潰,但會被掛起。我懷疑這個回調從來沒有被調用過,因爲這個例外。但是,這不是異步工作流程中的錯誤嗎? –

+1

這或(作爲文檔提示)第一個TAE被轉換成不同的東西,公共運行時以另一種方式關閉線程而不重新拋出它。當Async中的try/catch(with)與* normal * try/catch不同時,你可以在文檔中讀到計算工作流。但我肯定不是專家,但我懷疑這是你的問題。在其他情況下,我已經做出了相似的體驗 - 像TAE,StackOverflow等異常都是一個問題,在這種情況下關閉應用程序通常會更好。到目前爲止,我總是發現一個重新設計工作,沒有Abort – Carsten