비동기 컨텍스트에서 F# 예외 처리

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



예외 처리와 관련하여 동기 및 비동기 컨텍스트에서 예외를 포착하는 것의 근본적인 차이점은 구문: try-withAsync.Catch 입니다.
다음 몇 가지 예에서는 이러한 차이점과 비동기 컨텍스트 내에서 예외 처리의 적절한 사용법을 보여줍니다.

비동기 캐치


Async.Catch 비동기 작업 흐름 내에서 예외를 포착할 수 있는 비동기 계산을 생성합니다. Async<Choice<'T, exn>>를 반환하여 수행합니다. 여기서 'T
비동기 워크플로우 및 exn는 예외입니다.
이 결과 값은 패턴 매칭을 통해 추출할 수 있습니다.

다음을 기억하는 것이 중요합니다.
  • 비동기 작업 흐름이 성공적으로 완료되면(예외가 발생하지 않았음을 의미) Choice1Of2가 결과 값으로 반환됩니다.
  • 비동기 작업 흐름 내에서 예외가 발생하면 발생한 예외와 함께 Choice2Of2가 반환됩니다.

  • 비동기 워크플로에서 Async.Catch를 어떻게 사용할 수 있는지 살펴보겠습니다.

    예시 1. 예상 결과 처리 방법



    이 예에서는 Async.Catch가 예상 결과를 반환할 때(예외가 아닌 경우) someAsyncFunction를 사용하는 방법을 보여줍니다.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.Catch에서 예외가 발생했을 때 someAsyncFunction를 사용하는 방법을 보여줍니다.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: 중첩 함수



    비동기 컨텍스트 내의 중첩 함수의 경우 가장 내부 자식 함수가 예외를 throw하면 외부 함수의 모든 실행이 즉시 중지됩니다(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
  • 좋은 웹페이지 즐겨찾기