I have read an answer to a similar question https://stackoverflow.com/a/43841624/11478903 but it doesn't explain the yielding of execution.
I have a thread that consumes events using GetConsumingEnumerable from a BlockingCollection<Event> _eventQueue property.
public async Task HadleEventsBlocking()
{
    foreach (var event in _eventsQueue.GetConsumingEnumerable())
    {
        switch (event)
        {
            case Event.BtnAddClicked:
                HandleBtnAddClickedAsync(_cts.Token);
                break;
            case Event.BtnRemoveClicked:
                HandleBtnRemoveClickedAsync(_cts.Token);
                break;
            case Event.BtnDisableClicked:
                _cts.Cancel();
                break;
            case Event.BtnEnableClicked:
                _cts.Dispose();
                _cts = new CancellationTokenSource();
                break;
        }
        Console.WriteLine("Event loop execution complete.");
    }
}
public async Task HandleBtnAddClickedAsync(CancellationToken token)
{
    try
    {
        await Task.Run(async () =>
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            token.ThrowIfCancellationRequested();
            Console.WriteLine("BtnAddClicked event complete");
        });
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("HandleBtnAddClicked Cancelled");
    }
}
    
public async Task HandleBtnRemoveClickedAsync(CancellationToken token)
{
    try
    {
        await Task.Run(async () =>
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            token.ThrowIfCancellationRequested();
            Console.WriteLine("BtnRemoveClicked event complete");
        });
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("HandleBtnRemoveClicked Cancelled");
    }
}
And this does exactly what I want, the foreach loop executes each Event as fast as possible and does not get blocked. The methods that correspond to each Event also get the convenience of try/catch with the await Task.Run but why does this work? Because if I simply rearrange it won't work as I want it to.
public async Task HadleEventsBlocking()
{
    foreach (var event in _eventsQueue.GetConsumingEnumerable())
    {
        switch (event)
        {
            case Event.BtnAddClicked:
                try
                {
                    await Task.Run(async () =>
                    {
                        _cts.Token.ThrowIfCancellationRequested();
                        await Task.Delay(2000);
                        _cts.Token.ThrowIfCancellationRequested();
                        Console.WriteLine("BtnAddClicked event complete");
                    });
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("HandleBtnAddClicked Cancelled");
                }
                break;
            case Event.BtnRemoveClicked:
                try
                {
                    await Task.Run(async () =>
                    {
                        _cts.Token.ThrowIfCancellationRequested();
                        await Task.Delay(2000);
                        _cts.Token.ThrowIfCancellationRequested();
                        Console.WriteLine("BtnRemoveClicked event complete");
                    });
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("HandleBtnRemoveClicked Cancelled");
                }
    
                break;
            case Event.BtnDisableClicked:
                _cts.Cancel();
                break;
            case Event.BtnEnableClicked:
                _cts.Dispose();
                _cts = new CancellationTokenSource();
                break;
        }
        Console.WriteLine("Event loop execution complete.");
    }
}
Now each time an event is executed the foreach loop is blocked by the await inside the try/catch and I understand why, because of the await on the Task.Run.
However I don't understand why I get desired behavior when I pack it into a method that I don't await. Is it because the await inside yields execution back to HandleEventsBlocking and it resumes the foreach loop? I'd also appreciate a comment on whether this is good practice, it got me far but I just don't understand the tool I'm using it and it makes me worried.