非同期コンテキストでの例外処理


⚠️ All of the observations and examples in this article are valid for .NET 5 and below.


例外処理に関しては、同期と非同期コンテキストにおけるcatch catchの基本的な違いは構文です.try-withAsync.Catch .
次のいくつかの例では、この相違点と非同期コンテキスト内での例外処理の適切な使用方法を示します.

非同期。キャッチ

Async.Catch 非同期ワークフロー内で例外をキャッチできる非同期計算を作成します.を返すことによってAsync<Choice<'T, exn>> どこ'T の結果型
非同期ワークフローとexn 例外はスローされます.
この結果の値はパターンマッチングにより抽出できる.
覚えておくことが大切です.
  • 非同期ワークフローが正常に完了した場合、例外はスローされませんChoice1Of2 を返します.
  • 非同期ワークフロー内で例外が発生した場合、Choice2Of2 が送出されます.
  • の使い方を見てみましょうAsync.Catch 非同期ワークフローで.

    例1期待される結果を処理する方法


    この例ではAsync.CatchsomeAsyncFunction 期待される結果を返します(例外に対して).
    一度someAsyncFunction が完了するとfunctionExec は、Choice1Of2 を返し、コンソールに出力します.
    
    let someAsyncFunction(raiseException: bool) : Async<unit> =
        async {
            printfn ("Starting someAsyncFunction...")
            do! Async.Sleep(1000)
            if(raiseException) then
                raise (System.Exception("someAsyncFunction threw Exception"))
        }
    
    let functionExec(raiseException: bool) : Async<string> =
        async{
            let! result = someAsyncFunction(raiseException) |> Async.Catch
            return match result with
                    | Choice1Of2 _ -> "Result from someAsyncFunction"
                    | Choice2Of2 ex -> ex.Message
        }
    
    let main() =
        async{
            let! result = functionExec(false)
            printfn($"{result}")
        }
    
    Async.Start(main())
    Async.Sleep 1000 |> Async.RunSynchronously
    
    
    コンソール出力
    Starting someAsyncFunction...
    Result from someAsyncFunction
    

    例2例外結果の処理方法


    この例ではAsync.CatchsomeAsyncFunction 例外をスローします.
    一度someAsyncFunction 例外をスローするfunctionExec は、Choice2Of2 を返します.
    
    let someAsyncFunction(raiseException: bool) : Async<unit> =
        async {
            printfn ("Starting someAsyncFunction...")
            do! Async.Sleep(1000)
            if(raiseException) then
                raise (System.Exception("someAsyncFunction threw Exception"))
        }
    
    let functionExec(raiseException: bool) : Async<string> =
        async{
            let! result = someAsyncFunction(raiseException) |> Async.Catch
            return match result with
                    | Choice1Of2 _ -> "Result from someAsyncFunction"
                    | Choice2Of2 ex -> ex.Message
        }
    
    let main() =
        async{
            let! result = functionExec(true)
            printfn($"{result}")
        }
    
    Async.Start(main())
    Async.Sleep 1000 |> Async.RunSynchronously
    
    
    コンソール出力
    Starting someAsyncFunction...
    someAsyncFunction threw Exception
    

    例3 :入れ子関数


    最も内側の子関数が例外をスローしたときの非同期コンテキスト内のネストされた関数の場合、外部関数の実行のすべてをすぐに停止します(他の一般的な言語と同じように、Cのような).
    次の例はこのように見えます.
    
    let someChildAsyncFunction(raiseException: bool) : Async<unit> =
        async{
            printfn("Starting someChildAsyncFunction...")
            do! Async.Sleep(1000)
            if(raiseException) then
                raise (System.Exception("someChildAsyncFunction raised Exception"))
        }
    
    let someAsyncFunction(raiseException: bool) : Async<unit> =
        async {
            printfn ("Starting someAsyncFunction...")
            do! someChildAsyncFunction(raiseException)
            printfn ("Ending someAsyncFunction...")
        }
    
    let functionExec(raiseException: bool) : Async<string> =
        async{
            let! result = someAsyncFunction(raiseException) |> Async.Catch
            return match result with
                    | Choice1Of2 _ -> "Some result"
                    | Choice2Of2 ex -> ex.Message
        }
    
    let main() =
        async{
            let! result = functionExec(true)
            printfn($"{result}")
        }
    
    Async.Start(main())
    Async.Sleep 1000 |> Async.RunSynchronously
    
    
    コンソール出力
    Starting someAsyncFunction...
    Starting someChildAsyncFunction...
    someChildAsyncFunction raised Exception
    
    一度コンソールから見ることができますsomeChildAsyncFunction 例外をスローしますsomeAsyncFunction .

    試してみる


    今、我々はどのように見てきたAsync.Catch 我々が使用しようとするならば、起こることを見ましょうtry-with 非同期コンテキスト内
    
    let someAsyncFunction() : Async<unit> =
        async {
            printfn ("Starting someAsyncFunction...")
            do! Async.Sleep(1000)
            raise (System.Exception("someAsyncFunction threw Exception"))
        }
    
    try
        Async.Start(someAsyncFunction())
    with
        | Failure message -> printfn($"{message}")
    
    printfn("Hello, this example will blow up")
    
    
    コンソール出力
    Starting someAsyncFunction...
    Unhandled exception. System.Exception: someAsyncFunction threw Exception
       at [email protected](Unit _arg1)
       at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, b result1, FSharpFunc`2 userCode) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 464
       at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 139
    --- End of stack trace from previous location ---
       at [email protected](ExceptionDispatchInfo edi)
       at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 139
       at <StartupCode$FSharp-Core>[email protected](Object _arg2) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 1609
       at System.Threading.TimerQueueTimer.<>c.<.cctor>b__27_0(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
    --- End of stack trace from previous location ---
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.TimerQueueTimer.CallCallback(Boolean isThreadPool)
       at System.Threading.TimerQueueTimer.Fire(Boolean isThreadPool)
       at System.Threading.TimerQueue.FireNextTimers()
       at System.Threading.TimerQueue.AppDomainTimerCallback(Int32 id)
    
    Process finished with exit code 134 (interrupted by signal 6: SIGABRT)
    
    
    コンソール出力はtry-with 未処理の例外を取得しています.ここで起こるのは、例外がスレッドプールでスローされることですsomeAsyncFucntion が実行される.
    スレッドプールの未処理の例外はプロセスがAsync.Start そして、決して達しないtry-with ブロック.(ソース:MSDN - Exceptions in managed threads & Stack Overflow ).

    参考文献

  • Async Programming in F#

  • MSDN - Exceptions in managed threads

  • Stack Overflow
  • F# Async Guide