为何调用含await的异步方法不会阻塞父方法执行?
异步事件处理中的执行权移交与非阻塞逻辑疑问
场景背景
我通过BlockingCollection<Event> _eventQueue的GetConsumingEnumerable在单个线程中消费事件,遇到两种不同写法带来的效果差异,希望搞懂背后的执行逻辑,同时确认写法是否属于最佳实践。
第一种实现(符合预期:非阻塞处理事件)
public async Task HandleEventsBlocking() { 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"); } }
该实现效果符合预期:foreach循环能快速处理每个Event,不会被阻塞;同时事件处理方法可借助await Task.Run实现try/catch的便捷取消处理。
第二种实现(不符合预期:循环被阻塞)
public async Task HandleEventsBlocking() { 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."); } }
此时每次执行事件时,foreach循环会被try/catch内的await阻塞——这点我理解,因为直接await了Task.Run返回的任务。
核心疑问
- 为什么将逻辑封装为异步方法、不使用await调用时,就能获得预期的非阻塞效果?是否是因为异步方法内部的
await将执行权交还给HandleEventsBlocking,使其能继续foreach循环? - 第一种写法是否属于最佳实践?目前它能满足需求,但我担心对所用工具理解不足,会不会存在隐藏问题?
内容的提问来源于stack exchange,提问作者The_Matrix
相关产品推荐
相关产品推荐

