一、async/await 异步编程实现机制
1.1 核心概念
async/await
是 c# 5.0 引入的语法糖,它基于**状态机(state machine)**模式实现,将异步方法转换为编译器生成的状态机类。
1.2 编译器转换过程
当编译器遇到 async
方法时,会将其转换为一个实现了 iasyncstatemachine
接口的状态机类。
// 原始代码 public async task<int> getdataasync() { await task.delay(1000); return 42; }
编译器会将其转换为类似以下结构:
// 伪代码:编译器生成的状态机 [compilergenerated] private sealed class <getdataasync>d__1 : iasyncstatemachine { public int <>1__state; public asynctaskmethodbuilder<int> <>t__builder; public yourclass <>4__this; private taskawaiter <>u__1; public void movenext() { int num = <>1__state; try { taskawaiter awaiter; if (num != 0) { awaiter = task.delay(1000).getawaiter(); if (!awaiter.iscompleted) { <>1__state = 0; <>u__1 = awaiter; <>t__builder.awaitoncompleted(ref awaiter, ref this); return; } } else { awaiter = <>u__1; <>u__1 = default(taskawaiter); <>1__state = -1; } awaiter.getresult(); // 清理异常 <>t__builder.setresult(42); // 设置返回值 } catch (exception e) { <>1__state = -2; <>t__builder.setexception(e); return; } } public void setstatemachine(iasyncstatemachine statemachine) { <>t__builder.setstatemachine(statemachine); } }
1.3 关键组件解析
1.3.1 asynctaskmethodbuilder
- 负责管理异步方法的生命周期
- 包含
task
的创建、状态管理和结果设置 - 提供
awaitoncompleted
、setresult
、setexception
等方法
1.3.2 状态机工作流程
- 初始状态 (
<>1__state = -1
):方法开始执行 - 等待状态 (
<>1__state = 0
):遇到await
且任务未完成 - 完成状态 (
<>1__state = -2
):方法执行完毕或发生异常
1.3.3 await 操作的执行过程
- 调用
getawaiter()
获取taskawaiter
- 检查
iscompleted
属性 - 如果未完成:
- 保存当前状态
- 注册 continuation 回调
- 返回控制权给调用者
- 如果已完成:继续执行后续代码
1.4 上下文捕获(context capture)
await
默认会捕获当前的 synchronizationcontext 或 taskscheduler:
public async task processasync() { // 捕获当前上下文 await someasyncoperation(); // 回到原始上下文执行后续代码 updateui(); // 在ui线程上执行 }
二、死锁产生的原因
2.1 同步阻塞导致的死锁
最常见的死锁场景:在同步代码中阻塞等待异步操作完成。
// 危险代码 - 可能导致死锁 public int getdata() { // 死锁!等待异步方法完成 return getdataasync().result; } public async task<int> getdataasync() { await task.delay(1000); return 42; }
死锁形成过程:
getdata()
调用getdataasync()
getdataasync()
开始执行,遇到await
- 线程池线程被释放,
getdata()
在主线程阻塞等待 await
完成后,需要回到原始上下文(主线程)继续执行- 但主线程被
result
阻塞,无法执行 continuation - 形成死锁
2.2 ui线程死锁
在winforms/wpf应用中特别常见:
private async void button_click(object sender, eventargs e) { // 危险:在ui事件中同步等待 var result = getdataasync().result; textbox.text = result.tostring(); }
2.3 asp.net 经典死锁
在asp.net framework中:
public actionresult getdata() { // 可能死锁 var data = getdataasync().result; return json(data); }
三、死锁解决方案
3.1 根本原则:避免同步阻塞
错误做法:
// ❌ 避免使用 var result = doasync().result; var result = doasync().wait(); var result = doasync().getawaiter().getresult();
正确做法:
// ✅ 使用 async/await 链式调用 public async task<int> getdataasync() { return await getdataasync(); }
3.2 解决方案一:异步编程链
将同步方法改为异步:
// 原始同步方法 public int getdata() { return getdataasync().result; // 死锁风险 } // 改为异步方法 public async task<int> getdataasync() { return await getdataasync(); } // 调用者也需要异步 public async task processasync() { var data = await getdataasync(); // 处理数据 }
3.3 解决方案二:configureawait(false)
在类库中使用 configureawait(false)
避免上下文捕获:
public async task<int> getdataasync() { // 不捕获上下文,避免死锁 await task.delay(1000).configureawait(false); // 继续异步操作 await anotherasyncoperation().configureawait(false); return 42; }
使用场景:
- 类库开发
- 不需要访问ui组件的后台操作
- asp.net core 应用
3.4 解决方案三:创建新线程执行
当必须同步调用时,使用新线程:
public int getdata() { // 在新线程中执行异步方法 return task.run(async () => await getdataasync()).result; }
四、最佳实践
4.1 类库开发
// 类库中始终使用 configureawait(false) public async task<serviceresult> callserviceasync() { var response = await httpclient.getasync(url) .configureawait(false); var content = await response.content.readasstringasync() .configureawait(false); return jsonconvert.deserializeobject<serviceresult>(content); }
4.2 ui应用开发
// ui事件处理保持异步 private async void button_click(object sender, eventargs e) { try { button.enabled = false; var result = await getdataasync(); // 不使用 .result textbox.text = result.tostring(); } catch (exception ex) { messagebox.show(ex.message); } finally { button.enabled = true; } }
4.3 异步main方法
// .net 4.7.1+ 支持 async main static async task<int> main(string[] args) { try { await processasync(); return 0; } catch (exception ex) { console.writeline(ex.message); return 1; } }
五、总结
- async/await 是基于状态机的编译器魔法
- 死锁 主要由同步阻塞和上下文捕获引起
- 最佳解决方案 是保持异步调用链
- 类库开发 应使用
configureawait(false)
- 避免 在异步代码中使用
.result
和.wait()
遵循这些原则,可以安全高效地使用c#的异步编程模型。
到此这篇关于c# async await 实现机制详解的文章就介绍到这了,更多相关c# async await内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论