# Operation cancellation

Brevity is for kings as it requires context and context is for kings. Me.

GitHub and NuGet

How many times you have been writing something like this passing those tedious logger/token parameters?

interface IMyService
{
void Method1(…, ILogger logger, CancellationToken token);
void Method2(…, ILogger logger, CancellationToken token);
…
}


What we are about to do is to use a special Cancellation helper class like this:

    static void Main(string[] args)
{
using (new Cancellation())
{
Cancellation.Request();
}
}

{
try
{
while (!Cancellation.Requested)
{
WriteLine("Ping");
}

Cancellation.ThrowIfRequested();
}
catch(OperationCanceledException)
{
WriteLine("Ping cancelled");
}
}


Where Cancellation is defined as:

public class Cancellation : IDisposable
{
static AsyncLocal<CancellationTokenSource> Context { get; } =
new AsyncLocal<CancellationTokenSource>();

public Cancellation()
: this(CancellationToken.None)
{
}

public Cancellation(CancellationToken cancellationToken)
: this(cancellationToken, Timeout.InfiniteTimeSpan)
{
}

public Cancellation(int timeout)
: this(TimeSpan.FromMilliseconds(timeout))
{
}

public Cancellation(TimeSpan timeout)
: this(CancellationToken.None, timeout)
{
}

public Cancellation(CancellationToken cancellationToken, int timeout)
: this(cancellationToken, TimeSpan.FromMilliseconds(timeout))
{
}

public Cancellation(CancellationToken cancellationToken, TimeSpan timeout)
{
Parent = Context.Value;
Context.Value = CancellationTokenSource

Context.Value.CancelAfter(timeout);
}

public void Dispose()
{
var cts = Context.Value;
Context.Value = Parent;
cts.Dispose();
}

CancellationTokenSource Parent { get; }
public static CancellationToken Token =>
Context.Value?.Token ?? CancellationToken.None;

public static void Request() => Context.Value?.Cancel();
public static bool Requested => Token.IsCancellationRequested;
public static void ThrowIfRequested() => Token.ThrowIfCancellationRequested();
}

• This CancellationTokenSource Parent { get; } won't work in certain context switching situations that's why the T of AsyncLocal must be the class itself and link to itself too. I experimented with something similar to your solution and wanted to make it a generic AsycLocal helper but I had to use the Action<AsyncLocalValueChangedArgs<T>> ctor overload to make it work when the thread context changed. As this was too tricky I reverted it to use the simpler code. – t3chb0t Oct 3 '19 at 6:38
• This is the issue I experienced: Restore AsyncLocal.Value on ThreadContextChanged - the code was running fine with the debugger but it was failing when run under xunit where it was loosing its Value property of AsyncLocal. – t3chb0t Oct 3 '19 at 6:45
• Am I wrong to assume you couldn't use Cancellation twice at the same time? – IEatBagels Oct 10 '19 at 20:16
• @IEatBagels There is only one Cancellation in the context at a given moment, but you could create a nested one with using(new Cancellation(...) { ... }). You would need to pass old plain CancellationToken explicitly where two or more are required the same time at the same place though - rarely thing to happen. – Dmitry Nogin Oct 10 '19 at 22:10
• @IEatBagels Here you could find a ready to be tried demo. – Dmitry Nogin Oct 10 '19 at 22:12

Not sure if it counts as an answer, but the most important missing part was the following ASP.NET Core attribute:

public class AmbientContextAttribute : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
using (new Op(context.HttpContext.Request.GetDisplayUrl()))
using (new Cancellation(context.HttpContext.RequestAborted))
await next();
}
}

• By lack of more answers, I grant you the bounty for the combined question/answer. – dfhwze Oct 11 '19 at 19:51