簡體   English   中英

在異步中異常處理的意外行為,可能的錯誤?

[英]Unexpected behavior with exception handling in async, possible bug?

我在調用嵌套的異步時偶然發現了一個問題。 引發了異常,但無法使用異步工作流提供的任何常規異常處理方法。

以下是一個簡單的測試,它可以重現這個問題:

[<Test>]
let ``Nested async is null with try-with``() = 

    let g(): Async<unit> = Unchecked.defaultof<Async<unit>>

    let f = async {
            try
                do! g()
            with e ->  
                printf "%A" e
    }

    f |> Async.RunSynchronously |> ignore

這導致了以下異常:

System.NullReferenceException : Object reference not set to an instance of an object.
at Microsoft.FSharp.Control.AsyncBuilderImpl.bindA@714.Invoke(AsyncParams`1 args)
at <StartupCode$FSharp-Core>.$Control.loop@413-40(Trampoline this, FSharpFunc`2 action)
at Microsoft.FSharp.Control.Trampoline.ExecuteAction(FSharpFunc`2 firstAction)
at Microsoft.FSharp.Control.TrampolineHolder.Protect(FSharpFunc`2 firstAction)
at Microsoft.FSharp.Control.AsyncBuilderImpl.startAsync(CancellationToken cancellationToken,     FSharpFunc`2 cont, FSharpFunc`2 econt, FSharpFunc`2 ccont, FSharpAsync`1 p)
at Microsoft.FSharp.Control.CancellationTokenOps.starter@1121-1.Invoke(CancellationToken     cancellationToken, FSharpFunc`2 cont, FSharpFunc`2 econt, FSharpFunc`2 ccont, FSharpAsync`1 p)
at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously(CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously(FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
at Prioinfo.Urkund.DocCheck3.Core2.Tests.AsyncTests.Nested async is null with try-with() in SystemTests.fs: line 345 

我真的認為應該在這種情況下捕獲異常,或者這是否真的是預期的行為? (我正在使用Visual Studio 2010 Sp1進行記錄)

此外, Async.CatchAsync.StartWithContinuations表現出與這些測試用例所證明的相同的問題:

[<Test>]
let ``Nested async is null with Async.Catch``() = 

    let g(): Async<unit> = Unchecked.defaultof<Async<unit>>

    let f = async {
                do! g()
            }

    f |> Async.Catch |> Async.RunSynchronously |> ignore


[<Test>]
let ``Nested async is null with StartWithContinuations``() = 

    let g(): Async<unit> = Unchecked.defaultof<Async<unit>>

    let f = async {
                do! g()
            }

    Async.StartWithContinuations(f
                                , fun _ -> ()
                                , fun e -> printfn "%A" e
                                , fun _ -> ())

似乎在工作流構建器中的bind-method中引發了異常,我的猜測是,因此繞過了正常的錯誤處理代碼。 它似乎是異步工作流程實現中的一個錯誤,因為我沒有在文檔或其他地方發現任何暗示這是預期行為的內容。

在大多數情況下我很容易解決這個問題,所以至少對我來說這不是一個大問題,但它有點令人不安,因為它意味着你不能完全信任異步異常處理機制,以便能夠捕獲所有異常。

編輯:

在考慮之后我同意kvb。 Null asyncs實際上不應該存在於普通代碼中,只有當你做了一些你可能不應該做的事情時(例如使用Unchecked.defaultOf)或者使用反射來生成值(在我的例子中它是一個涉及的模擬框架) 。 因此,這不是一個真正的錯誤,而是一個邊緣案例。

我不認為這是一個錯誤。 由於名稱指示Unchecked.defaultof<_>不檢查它生成的值是否有效,並且Async<unit>不支持null作為正確值(例如,如果您嘗試使用let x : Async<unit> = null請參閱消息let x : Async<unit> = null )。 Async.Catch等旨在捕獲異步計算中拋出的異常,而不是在編譯器后面隱藏並創建無效異步計算所導致的異常。

我完全同意kvb - 當你使用Unchecked.defaultOf初始化一個值時,這意味着使用該值的行為可能是未定義的,因此不能將其視為bug。 在實踐中,您不必擔心它,因為您永遠不應該獲得Async<'T>類型的null值。

要添加更多詳細信息,無法處理異常,因為翻譯如下所示:

async.TryWith
  ( async.Bind ( Unchecked.defaultof<_>, 
                 fun v -> async { printfn "continued" } ), 
    fun e -> printfn "%A" e)

唯一的例外是從拋出Bind返回的工作流程之前,方法Bind啟動(它發生之后您調用RunSynchronously ,因為工作流程使用包裹Delay ,但它發生在工作流執行之外)。 如果要處理這種異常(由於構造不正確的工作流而產生),您可以編寫一個運行工作流的TryWith版本,並處理在執行之外拋出的異常:

let TryWith(work, handler) = 
  Async.FromContinuations(fun (cont, econt, ccont) ->
    try
      async { let! res = work in cont res }
      |> Async.StartImmediate
    with e -> 
      async { let! res = handler e in cont res } 
      |> Async.StartImmediate )   

然后你可以處理這樣的異常:

let g(): Async<unit> = Unchecked.defaultof<Async<unit>> 
let f = 
    TryWith
      ( (async { do! g() }),
        (fun e -> async { printfn "error %A" e }))
f |> Async.RunSynchronously

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM