2012-01-19 6 views
3

nullになるネストされたAsyncを呼び出すときに問題が発生しました。例外が発生しますが、非同期ワークフローが提供する通常の例外処理メソッドではキャッチできません。非同期の例外処理で予期しない動作が発生する可能性がありますか?

は、次のような問題を再現する簡単なテストです:

follwing例外が発生
[<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 [email protected](AsyncParams`1 args) 
at <StartupCode$FSharp-Core>[email protected](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 [email protected]oke(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 
私は本当に例外が、この場合に巻き込まれるべきだと思う、またはある

これは本当に期待される行動ですか?例外がバインドに内上げているようだ

[<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 _ ->()) 

また、Async.CatchAsync.StartWithContinuations展示これらのテストケースによって示されるように、同じ問題を(私は、Visual Studioにレコードの2010 SP1を使用しています)メソッドを使用すると、結果として通常のエラー処理コードがバイパスされることになります。ドキュメントや他の場所に何も見つからないので、これが意図された動作であることを示唆しているので、私には非同期ワークフローの実装におけるバグのように見えます。

ほとんどの場合、回避するのは簡単ですが、少なくとも私にとっては大きな問題ではないと思いますが、非同期の例外処理メカニズムを完全に信用できないため、少し不安ですすべての例外をキャプチャします。

編集:

それを私がKVBに同意するいくつかの考えを与えた後。 null非同期は通常のコードでは実際には存在すべきではありません。実際には、(Unchecked.defaultOfを使用するなどしてはいけない)何かをしたり、リフレクションを使って値を生成したりするだけの場合にしか生成できません(私の場合、 。したがって、実際にはバグではありませんが、もっと重要なケースです。

+0

VSでデバッグするときとエクスプローラで起動したときの両方で、FSIとコンソールアプリケーションで再現できます。だからそれは一貫しているようだ –

+0

Option.Noneは、基本的にnullです。だからAsync <'a option>この問題があるようです。 – Hans

答えて

4

私はそれがバグだとは思わない。名前がUnchecked.defaultof<_>であるため、生成する値が有効であることが確認されず、Async<unit>nullを適切な値としてサポートしません(たとえば、let x : Async<unit> = nullを使用しようとするとメッセージが表示されます)。 Async.Catchなどは、コンパイラの背後に潜んでいて無効な非同期計算を作成することによって引き起こされる例外ではなく、非同期計算内でスローされる例外を捕捉することを意図している。

+0

私はあなたのポイントを見ることができますが、一方で、構文の種類は、例外がキャッチされるべきであることを期待しています。通常のtry-catchブロックでnullデリゲートを呼び出すと、例外がキャッチされます。 –

+0

@SamuelOtter - 一般に、 'Unchecked.defaultof <_>'を使用すると、nullを有効な表現として持たない型のnull値を作成するときに問題が頻繁に発生します。デリゲートの場合、nullは完全に受け入れられる値です(たとえば、 'let x:System.Action = null'ではコンパイラに問題はありません)。 – kvb

+0

私が知っているのは、Unchecked.defaultOfは、テストケースでヌル値を引き起こすために、テストでのみ使用されます。私はnull非同期は実際にはまれでなければならないことに同意しますが、結局それが起こる可能性があります。私は非同期メソッドを使って擬似オブジェクトを持っていたが期待はなく、擬似フレームワークのデフォルトの動作はすべてのメソッドに対してnullを返すだけだったので、これを最初に見つけました。しかし、バグと呼ぶのはちょっと強いかもしれないと私は同意します。それは、文書化されていないほど重要なケースです。 –

4

私はkvbと完全に同意します - Unchecked.defaultOfを使用して値を初期化すると、値を使用する動作が定義されていない可能性があるため、バグとして扱うことはできません。実際には、nullの値を決してAsync<'T>のタイプにするべきではないので、それについて心配する必要はありません。

次のように翻訳が見えるので、いくつかの詳細を追加するには、例外は、処理することができません。

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

例外がBindによって返されたワークフローが開始され前Bind方法からスローされます(それワークフローはDelayを使用してラップされているため、ワークフローの実行の範囲外で発生するため、RunSynchronouslyに電話した後に発生します)。

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) 

次に、あなたのような例外を処理することができます:あなたは(間違って構築されたワークフローから生じる)例外のこの種類を扱いたい場合は、ワークフローを実行し、実行の外で発生した例外を処理TryWithのバージョンを書くことができます

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

私はそれが本当にバグではないことに同意するが、それは私に起こったので、実際には起こりえないということに同意しなければならない。私の場合、それは単体テストであったし、フレームを模擬したものであったため、それほど問題にはならなかった(たとえ問題が何かを理解する前に自分のコードが壊れたと思っていたとしても)。慣用的なF#コードでは、これは起こるべきではありませんが、私は、例えば、DIコンテナを使ってAsync-valuesやそのようなものを作成したり挿入したりすることで、 –

関連する問題