异步异常处理的意外行为,可能的错误?

Sam*_*ter 4 f# asynchronous exception-handling async-workflow

在调用恰好为空的嵌套异步时,我偶然发现了一个问题。引发了异常,但无法使用异步工作流提供的任何正常异常处理方法捕获它。

以下是重现问题的简单测试:

[<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
Run Code Online (Sandbox Code Playgroud)

这导致以下异常:

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 
Run Code Online (Sandbox Code Playgroud)

我真的认为在这种情况下应该捕获异常,或者这真的是预期的行为吗?(我使用 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 _ -> ())
Run Code Online (Sandbox Code Playgroud)

似乎异常是在工作流构建器的绑定方法中引发的,我的猜测是,因此绕过了正常的错误处理代码。对我来说,它看起来像是异步工作流实现中的一个错误,因为我在文档或其他地方没有发现任何表明这是预期行为的内容。

在大多数情况下,我认为这很容易解决,所以至少对我来说这不是一个大问题,但它有点令人不安,因为这意味着您不能完全相信异步异常处理机制能够捕获所有异常.

编辑:

经过一番思考,我同意kvb。空异步不应该真正存在于普通代码中,并且只有在您执行可能不应该执行的操作(例如使用 Unchecked.defaultOf)或使用反射来生成值(在我的情况下,它是一个涉及的模拟框架)时才能真正生成. 因此,这不是真正的错误,而是更多的边缘情况。

kvb*_*kvb 5

我不认为这是一个错误。顾名思义,Unchecked.defaultof<_>它不检查它产生的值是否有效,Async<unit>也不支持null作为正确的值(例如,如果您尝试使用,请参阅消息let x : Async<unit> = null)。 Async.Catch等旨在捕获异步计算中抛出的异常,而不是由于潜入编译器背后并创建无效异步计算而引起的异常。