15. Hosted services and background work
Sections
Reading settings
Natural voice recorded for this course, the same in every browser.
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
An IHostedService participates in the host lifetime. BackgroundService supplies the long-running ExecuteAsync loop, but the host still owns startup, cancellation and shutdown.
Prerequisites
Section titled “Prerequisites”Complete dependency injection and options and channels first. You should already understand service lifetimes, cancellation tokens and asynchronous streams.
The host owns lifetime
Section titled “The host owns lifetime”The experiment starts one host, waits on explicit completion gates, processes two items, then asks the host to stop. There is no Thread.Sleep and every wait has a five-second bound.
In .NET 10, all of BackgroundService.ExecuteAsync runs on a background thread. Work that must happen synchronously before other hosted services start belongs in a constructor, StartAsync, or IHostedLifecycleService—not before the first await in ExecuteAsync.
A bounded queue is an application contract
Section titled “A bounded queue is an application contract”The worker consumes a bounded Channel<T>. BoundedChannelFullMode.Wait applies backpressure when the queue is full. A real submission API should await WriteAsync; this small proof uses TryWrite because its capacity and two writes are fixed.
AddHostedService registers the worker as a singleton. A scoped dependency must therefore be resolved inside a fresh IServiceScope for every item. Injecting it into the worker constructor would turn a per-operation object into a captive dependency.
== The host starts one singleton worker and the queue owns the workstartup observed: Truequeued results: C major@scope-1 | G major@scope-2fresh scope per item: Trueshutdown cancellation observed: TrueFaults are policy
Section titled “Faults are policy”The default .NET 10 host policy stops the application when a BackgroundService throws. The proof sets BackgroundServiceExceptionBehavior.StopHost explicitly, releases a fault gate and observes ApplicationStopping:
== A BackgroundService fault stops its hostfault requested host stop: TrueStopping the host is not recovery. Production code still needs an explicit retry, dead-letter or operator policy around work that may be safely repeated.
Run the proof
Section titled “Run the proof”dotnet run --project code/csharp-advanced/Advanced -c Release -- l15The checked transcript is expected/l15.txt.
Exercises
Section titled “Exercises”- Replace
TryWritewith an asynchronous submission API and prove a third item waits while the two-slot queue is full. - Add one failure result to each work item without terminating the singleton worker.
- Introduce a second consumer and state which ordering guarantee is lost.
Solutions
- Return
ValueTaskfrom submission, awaitWriter.WriteAsync, and use gates to hold the first two items. Release one gate and assert the third write completes—never infer blocking from a delay. - Catch the operation exception around one item, complete its
TaskCompletionSourcewith a typed failure, and continue the read loop. Do not catch host cancellation as a work failure. - Set
SingleReader = falseand run two workers. FIFO still governs reads, but completion order now depends on processing; add sequence numbers if downstream code needs reordering.