Production engineering
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
for(int i=0;i<2;i++) { using var item=new Owned(); Console.WriteLine("scope:"+item.Id); }
Console.WriteLine("live:"+Owned.Live); Console.WriteLine("disposed:"+Owned.Disposed);
sealed class Owned:IDisposable { private static int next; public static int Live; public static int Disposed; private bool disposed; public int Id {get;}=++next; public Owned(){Live++;} public void Dispose(){if(disposed)return;disposed=true;Live--;Disposed++;} }
Reveal answer and reasoning
The output lines, in order, are “scope:1”; “scope:2”; “live:0”; “disposed:2”. The portable .NET9 console program creates an Owned object in each of two loop iterations. A using declaration inside the loop body disposes that iteration's object before the next iteration begins. Constructor accounting assigns IDs one and two and increments Live; idempotent Dispose decrements Live and increments Disposed once. The output is scope:1, scope:2, live:0 and disposed:2.
These are real IDisposable calls over ordinary objects, with serialized counters. The program runs no dependency-injection container, background service or channel. Its purpose is deterministic ownership reasoning, while actual framework scopes are exercised only by the external project.
A full queue can reject admission with429, and a canceled request has a different result from a completed operation. Completing a channel during shutdown gives accepted work an opportunity to finish within the host's stop budget, but a crash can bypass that opportunity entirely. The example's processing function is tightly bounded and its scopes are not shared between items. This does not justify using a context or another scoped dependency concurrently merely because it came from DI.
Queue bounds, request cancellation, per-item failure handling and durable acknowledgment must each have their own owner when extending the worker beyond its classroom behavior.
What a strong answer covers
- Exact output: scope:1 | scope:2 | live:0 | disposed:2
- The portable .NET9 console program creates an Owned object in each of two loop iterations.
- A full queue can reject admission with429, and a canceled request has a different result from a completed operation.
Common pitfall
A full queue can reject admission with429, and a canceled request has a different result from a completed operation. Completing a channel during shutdown gives accepted work an opportunity to finish within the host's stop budget, but a crash can bypass that opportunity entirely. The example's processing function is tightly bounded and its scopes are not shared between items. This does not justify using a context or another scoped dependency concurrently merely because it came from DI. Queue bounds, request cancellation, per-item failure handling and durable acknowledgment must each have their own owner when extending the worker beyond its classroom behavior.
Follow-up questions
- Why would replacing a scoped dependency with a singleton merely to avoid disposal change correctness rather than just allocation cost?
- What race would appear if the TaskCompletionSource were signaled before the operation scope was disposed?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.