Interprocess CancellationToken
Let’s refactor it a little bit for clarity and symmetry. We could have this class shared:

class NamedTokenSource : CancellationTokenSource
{
    const string Namespace = "4978406A-A7C0-4DE7-93C8-6BDB1145ED32";

    public static implicit operator NamedTokenSource(string name) =&gt;
        new NamedTokenSource(name);

    public NamedTokenSource(string name)
        : this(new EventWaitHandle(false, EventResetMode.ManualReset, name + Namespace))
    {            
    }

    public NamedTokenSource(EventWaitHandle handle)
    {
        Handle = handle;
        ThreadPool.RegisterWaitForSingleObject(handle, (s, to) =&gt; Cancel(), null, -1, true);
        Token.Register(() =&gt; handle.Set());            
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (disposing)
            Handle.Dispose();
    }

    EventWaitHandle Handle { get; }
}


Now Controller looks like:

NamedTokenSource cts = "WorkerTokenSource";
Console.WriteLine("Press ENTER to cancel...");
Console.ReadLine();
cts.Cancel();


when Worker is:

NamedTokenSource cts = "WorkerTokenSource";
Console.WriteLine("Waiting for cancellation...");            
cts.Token.WaitHandle.WaitOne();