Why does Async.Start seemingly propagate uncatchable exceptions?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
In F#, Async.Start fires off an async computation on the thread pool and does not return a handle to observe its result. If the computation throws an exception, there is no calling code to catch it — the exception propagates to the thread pool's unhandled exception handler, which by default crashes the process. This makes exceptions from Async.Start appear "uncatchable." The fix is to handle exceptions inside the async block, use Async.StartWithContinuations for explicit error callbacks, or use Async.StartAsTask to get a Task whose exception can be observed.
The Problem
Common Pitfalls
- Wrapping
Async.Startintry/withexpecting to catch the async exception:Async.Startreturns immediately without throwing. Thetry/withonly catches exceptions from theStartcall itself (which never throws). Handle exceptions inside the async block or useStartWithContinuations. - Using
Async.Startfor work whose errors must be observed: If you need to know whether the computation succeeded or failed,Async.Startis the wrong choice. UseAsync.StartAsTaskto get aTaskyou can inspect, orAsync.StartWithContinuationsfor callbacks. - Ignoring cancellation in long-running async computations:
Async.Startaccepts an optionalCancellationToken, but the computation must cooperate by checking for cancellation (usingAsync.Sleep,Async.CancellationToken, etc.). CPU-bound loops without cancellation points cannot be cancelled. - Calling
Async.RunSynchronouslyon the UI thread: This blocks the UI thread, causing the application to freeze. UseAsync.StartImmediateorAsync.StartwithSynchronizationContextfor UI-bound work. - Not setting
SetObserved()onUnobservedTaskException: In .NET 4.0, unobserved Task exceptions crashed the process by default. In .NET 4.5+, they are swallowed silently. Relying on either behavior is fragile — always handle exceptions explicitly.
Summary
Async.Startfires and forgets — exceptions crash the process because no code observes them- Handle exceptions inside the async block with
try/withfor simple cases - Use
Async.StartWithContinuationsfor explicit success/error/cancel callbacks - Use
Async.Catchto convert exceptions intoChoicevalues for composition - Use
Async.StartAsTaskwhen interoperating with Task-based code or when you need to observe the result
Related reading
- Why Does Await Not Appear to Prevent Second Operation on EF Context
- Why does Exception from async void crash the app but from async Task is swallowed
- Why does holding a non-Send type across an await point result in a non-Send Future?
- Why does Interlocked.Exchange not support Boolean type?
- Why does celery add thousands of queues to rabbitmq that seem to persist long after the tasks completel?
- Why does celery.control.inspect report fewer queued tasks than rabbitmqctl?
- Why does Interlocked.Exchange not support Boolean type?
- Why does invokeLater execute in the main thread?
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.