Я наткнулся на проблему при вызове вложенного Async, который оказывается нулевым. Возникает исключение, но оно не может быть перехвачено ни одним из обычных методов обработки исключений, которые обеспечивают асинхронные рабочие процессы.
Ниже приведен простой тест, который воспроизводит проблему:
[<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.Catch
и Async.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 _ -> ())
Кажется, исключение возникает в методе связывания в конструкторе рабочих процессов, и я предполагаю, что в результате обычный код обработки ошибок будет обойден. Для меня это похоже на ошибку в реализации асинхронных рабочих процессов, так как я не нашел ничего в документации или где-либо еще, что бы указывало на то, что это предполагаемое поведение.
В большинстве случаев обойтись довольно просто, я думаю, поэтому это не большая проблема, по крайней мере, для меня, но это немного тревожит, поскольку это означает, что вы не можете полностью доверять механизму обработки исключений асинхронного режима, чтобы иметь возможность захватить все исключения.
Редактировать:
Подумав немного, я согласен с kvb. Нулевые асинхронности в действительности не должны существовать в обычном коде и могут быть созданы только в том случае, если вы делаете что-то, чего вы, вероятно, не должны (например, используете Unchecked.defaultOf) или используете отражение для получения значений (в моем случае это была фреймворк) , Таким образом, это не совсем ошибка, а скорее крайний случай.