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.
However I don't understand why I get desired behavior when I pack it into a method that I don't await.Because you don't await. You'd have the same result if you didn't await theTask.Run.try/catchon theawait Task.Runinside of my work methods with working error handling. I can't do that if I don't wrap it inside of a method.awaitto method calls in your first implementation - you will get the same result. First approach is calledasync looppattern (mine naming), second one - just simple sequential loop.awaitmeansasynchronous wait: you will not get next work item until complete current one.HadleEventsBlockingshould give you a compiler warning about anasyncmethod that lacksawait. TheBlockingCollection<T>is intended for blocking threads. If instead of threads you prefer asynchronous flows (async methods) that lack thread affinity and identity, the correct queue to use is theChannel<T>. For more details see this question: Is there anything like asynchronousBlockingCollection<T>?