F# Exception Handling in Asynchronous Context
Tags: beginners, fsharp, programming, dotnet
Published: 2022-04-03 12:56:22

> :warning: **All of the observations and examples in this article are valid for .NET 5 and below.**

When it comes to exception handling the fundamental difference between catching exceptions in synchronous and asynchronous context is syntax: `try-with` vs `Async.Catch`.  
The next few examples will demonstrate this difference as well as the proper usage of exception handling within the asynchronous context.

## Async.Catch

`Async.Catch` creates an asynchronous computation that enables catching exceptions within the asynchronous workflow. It does that by returning an `Async<Choice<'T, exn>>` where `'T` is the result type of the
asynchronous workflow and `exn` is the exception thrown.  
The values of this result can be extracted by pattern matching.

It is important to remember:
* If the asynchronous workflow is completed successfully, meaning no exceptions were thrown, then a `Choice1Of2` is returned as the result value.
* If an exception occurs within the async workflow then a `Choice2Of2` is returned with the raised exception.

Let's see how we can use `Async.Catch` in asynchronous workflows.

### Example 1. How to handle an expected result

This example shows how to use `Async.Catch` when `someAsyncFunction` returns an expected result (as opposed to an exception).  
Once `someAsyncFunction` is complete, the `functionExec` will perform pattern matching which will result in `Choice1Of2` being returned and then printed to the console.

```FSharp

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

```
**Console output**

```
Starting someAsyncFunction...
Result from someAsyncFunction
```
### Example 2. How to handle exception result

This example shows how to use `Async.Catch` when `someAsyncFunction` throws an exception.  
Once `someAsyncFunction` throws an exception `functionExec` will perform pattern matching which will result in `Choice2Of2` (the exception) being returned and then printed to the console.

```FSharp

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

```
**Console output**

```
Starting someAsyncFunction...
someAsyncFunction threw Exception
```

### Example 3: Nested Functions

In the case of nested functions within the asynchronous context when the most inner child function throws an exception, it will immediately stop all of the executions of the outer functions (just like in other popular languages, like C#).
The next example will demonstrate how this looks like.

```FSharp

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

```
**Console output**

```
Starting someAsyncFunction...
Starting someChildAsyncFunction...
someChildAsyncFunction raised Exception
```

We can see from the console that once `someChildAsyncFunction` throws exception it also terminates further execution of its caller `someAsyncFunction`.

## Try-With

Now that we've seen how the `Async.Catch` let's see what happens if we try to use `try-with` within the asynchronous context

```FSharp

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")

```
**Console output**

```
Starting someAsyncFunction...
Unhandled exception. System.Exception: someAsyncFunction threw Exception
   at FSI_0015.someAsyncFunction@5-49.Invoke(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 Microsoft.FSharp.Control.AsyncPrimitives.Start@1077-1.Invoke(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>.$Async.Sleep@1603-3.Invoke(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)

```
The console output shows that by using `try-with` we're getting an unhandled exception. What happens here is that the exception is thrown in a thread pool where `someAsyncFucntion` is executed.  
Unhandled exceptions in thread pool terminate the process never propagating to `Async.Start` and never reaching the `try-with` block. (Source: [MSDN - Exceptions in managed threads](https://docs.microsoft.com/en-us/dotnet/standard/threading/exceptions-in-managed-threads) & [Stack Overflow](https://stackoverflow.com/questions/55693931/why-does-async-start-seemingly-propagate-uncatchable-exceptions/55694256#55694256)).

## References
- [Async Programming in F#](https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/async)
- [MSDN - Exceptions in managed threads](https://docs.microsoft.com/en-us/dotnet/standard/threading/exceptions-in-managed-threads)  
- [Stack Overflow](https://stackoverflow.com/questions/55693931/why-does-async-start-seemingly-propagate-uncatchable-exceptions/55694256#55694256)  
- [F# Async Guide](https://medium.com/@eulerfx/f-async-guide-eb3c8a2d180a)