Memory & GC
using System.Threading;
var state = new RequestState();
using var source = new CancellationTokenSource();
using (source.Token.Register(static value => ((RequestState)value!).Calls++, state))
{
source.Cancel();
Console.WriteLine($"inside:{state.Calls}");
}
source.Cancel();
Console.WriteLine($"after:{state.Calls}");
Console.WriteLine($"cancelled:{source.IsCancellationRequested}");
sealed class RequestState { public int Calls; }
Reveal answer and reasoning
The program creates one RequestState and one token source. Register attaches a callback that increments Calls on the explicitly supplied state. Calling Cancel inside the using block invokes that callback once, so inside:1 is printed. Leaving the block disposes the registration. Calling Cancel again does not turn cancellation into a recurring event; the token source remains cancelled, and Calls remains one.
The final lines therefore print after:1 and cancelled:True. These values establish callback behavior and cancellation state in a single-threaded bounded example. They do not measure the size of a state machine or prove that a collector reclaimed the registration. The state local itself remains in use for the output. Explain the real ownership path before drawing a heap diagram from these counts.
The event scenario in the lab starts with both a screen root and a static publisher root. Close the screen by disabling its root. The publisher still reaches the delegate, subscriber and payload. Disable the event subscription next. That removes the second path. The lesson is not that every event is a leak: short-lived publishers can die with their subscribers, while long-lived publishers require deliberate subscription ownership.
For actual async incidents, group retained payloads by operation state and waiting reason. A slow downstream service can extend legitimate lifetimes across thousands of concurrent requests. Correlate in-flight counts, cancellation response and retained types with the same time interval. A larger heap during a downstream outage may be overload retention rather than a permanently forgotten static collection.
What a strong answer covers
- Pending work can extend ownership beyond a method return.
- A static callback avoids lexical capture but explicit state is still strongly owned.
- Cancellation request, callback disposal and task completion are distinct events.
Common pitfall
Assuming a static callback retains no explicit state, or that requesting cancellation ends every outstanding lease.
Follow-up questions
- Can CancellationTokenRegistration.Dispose coordinate with a concurrently running callback?
- What happens to a large request state while a downstream task stays incomplete?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.