c# C#异步编程模式的演进历史

来源:这里教程网 时间:2026-02-21 17:37:24 作者:

BeginInvoke
async/await
:C# 异步模型的三次关键切换

早期 .NET 2.0 的异步编程靠手写

IAsyncResult
模式,代码嵌套深、错误处理难、调试困难。这不是“写法偏好”问题,而是运行时根本没提供上下文延续能力——
EndInvoke
返回后,原始栈帧早已销毁。

Task
类型出现前的三大模式及其崩溃点

.NET 3.5–4.0 间并存三种异步写法,但都绕不开状态机手动维护:

BeginXxx/EndXxx
(APM):必须配对调用,
EndXxx
被遗漏会导致线程挂起或资源泄漏
Event-based Async Pattern(EAP)
:如
WebClient.DownloadStringAsync
,事件回调中无法用
return
传递结果,异常只能靠
RunWorkerCompletedEventArgs.Error
传递
手动创建
Thread
ThreadPool.QueueUserWorkItem
:完全脱离调度器控制,
SynchronizationContext
无法自动捕获,UI 线程更新必崩

async/await
不是语法糖,而是编译器+运行时协同重构状态机

真正改变游戏规则的是 C# 5.0 + .NET 4.5 的组合:

async
方法被编译为
Task
-返回的状态机类,而
await
表达式会触发
GetAwaiter().OnCompleted()
注册回调,并在恢复时自动切回原
SynchronizationContext
(如 WinForms 的
Control.InvokeRequired
场景)。

这意味着:

不再需要显式
ContinueWith
链式调用,嵌套深度归零
try/catch
可直接捕获异步操作中的异常,无需拆解
AggregateException
ConfigureAwait(false)
成为性能关键开关:后台服务中不加它,每次 await 后都尝试切回原始上下文,徒增调度开销
public async Task<string> FetchDataAsync()
{
    // 下面这行 await 完成后,线程可能已切换
    // 但 this.InvokeRequired 仍能正确判断是否需跨线程
    var result = await httpClient.GetStringAsync("https://api.example.com");
    return result.ToUpper();
}

现代项目里还可能踩到的兼容性暗坑

即便用着 C# 10,只要目标框架是

net472
或更低,
ValueTask
就无法享受结构体优化;而
net6.0+
async
方法若返回
Task
却未真正异步(比如直接
return Task.FromResult(...)
),就会多分配一个状态机对象。

更隐蔽的是:ASP.NET Core 2.1+ 默认禁用

HttpContext.Capture
,导致在中间件中
await
后访问
HttpContext.Request
可能抛出
ObjectDisposedException
——这不是代码写错,而是运行时生命周期管理逻辑变了。

相关推荐