Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Async.Start seemingly propagate uncatchable exceptions?

Consider this console app:

let throwAsync = 
  async { failwith "" }

[<EntryPoint>]
let main argv =
  try Async.Start throwAsync
  with _ -> printfn "Failed"
  System.Console.Read() |> ignore
  0

The app crashes immediately when run. This doesn't make sense to me, for two reasons:

  1. AFAIK (e.g. this blog post) exceptions should not bubble up through Async.Start (rendering the try ... with pointless, but it's there for point 2)
  2. The (surprisingly) throwing code is surrounded by try ... with, but the exception is not caught (it never prints "Failed", and again, the app crashes).

What's going on?

like image 816
cmeeren Avatar asked Apr 15 '19 16:04

cmeeren


People also ask

Why are asynchronous exceptions uncatchable?

An asynchronous exception is uncatchable because the intended catch block is not present when the asynchronous callback is executed. Instead, the exception will propagate all the way and terminate the program. Does taking a callback make a function asynchronous?

How do you catch exceptions in async void method?

With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started. Figure 2 illustrates that exceptions thrown from async void methods can’t be caught naturally.

What happens if you don’t await a method in async?

If you do not await, any exceptions that occur in the calling method will be swallowed. Essentially you are turning the method call into a fire and forget. Below is a simple example that demonstrates two behaviors of calling await on an async. To run the example, just copy and paste in a new console application.

Why do async event handlers return void?

Event handlers naturally return void, so async methods return void so that you can have an asynchronous event handler. However, some semantics of an async void method are subtly different than the semantics of an async Task or async Task<T> method.


1 Answers

The exception is thrown on a threadpool thread where the async block executes.

So yes, this means that the exception is not propagated to the thread that ran Async.Start, and the try-with block is never hit. But also, it means that the exception is now thrown elsewhere, and without any exception handling it will crash your app.

Quoting MSDN:

Unhandled exceptions in thread pool threads terminate the process. There are three exceptions to this rule:

  • A System.Threading.ThreadAbortException is thrown in a thread pool thread because Thread.Abort was called.
  • A System.AppDomainUnloadedException is thrown in a thread pool thread because the application domain is being unloaded.
  • The common language runtime or a host process terminates the thread.

For more information, see Exceptions in Managed Threads.

like image 170
scrwtp Avatar answered Nov 15 '22 13:11

scrwtp