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);
    …
}


Enough is enough. Please see here about ambient logging. Below is about ambient cancellation.

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

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

    static async Task PingAsync()
    {
        try
        {
            while (!Cancellation.Requested)
            {
                await Task.Delay(100, Cancellation.Token);
                WriteLine("Ping");
            }

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


Where Cancellation is defined as:

public class Cancellation : IDisposable
{
    static AsyncLocal&lt;CancellationTokenSource&gt; Context { get; } = 
        new AsyncLocal&lt;CancellationTokenSource&gt;();

    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
            .CreateLinkedTokenSource(cancellationToken);

        Context.Value.CancelAfter(timeout);
    }

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

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

    public static void Request() =&gt; Context.Value?.Cancel();
    public static bool Requested =&gt; Token.IsCancellationRequested;
    public static void ThrowIfRequested() =&gt; Token.ThrowIfCancellationRequested();        
}
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();
    }
}