If you're creating a lot of short-lived operations, repeatedly allocating CancellationTokenSource instances can add avoidable pressure.

In .NET, CancellationTokenSource.TryReset() lets you reuse a source when it's safe to do so.

The Basic Pattern

var cts = new CancellationTokenSource();

for (var i = 0; i < 1000; i++)
{
    using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
    using var linked = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);

    await ProcessMessageAsync(queue[i], linked.Token);

    if (!cts.TryReset())
    {
        cts.Dispose();
        cts = new CancellationTokenSource();
    }
}

The important bit is the fallback. TryReset() can return false, and you should handle that path cleanly.

When Reuse Is Safe

I treat reuse as an optimization only for controlled loops where:

  • all work using the token has completed
  • callbacks and registrations are no longer active
  • no other thread still references the old token lifecycle

If those guarantees are fuzzy, just allocate a new source and keep things simple.

A Practical Handler Example

private CancellationTokenSource _loopCts = new();

public async Task RunBatchAsync(IReadOnlyList<Job> jobs, CancellationToken stopToken)
{
    foreach (var job in jobs)
    {
        stopToken.ThrowIfCancellationRequested();

        using var linked = CancellationTokenSource.CreateLinkedTokenSource(_loopCts.Token, stopToken);
        await _processor.HandleAsync(job, linked.Token);

        if (!_loopCts.TryReset())
        {
            _loopCts.Dispose();
            _loopCts = new CancellationTokenSource();
        }
    }
}

This keeps the hot path lean while still preserving correctness.

Wrapping Up

TryReset() is a useful micro-optimization for high-throughput loops, but only when lifecycle boundaries are crystal clear.

Use it where you're confident about ownership, keep the fallback allocation, and favor correctness over cleverness.