C# async/await、Task 、死锁
<h1>一、核心</h1><div> </div>
<ul>
<li>Task:代表一个尚未完成的操作(可以是异步、也可以是同步)</li>
<li>async/await:语法糖,让异步代码写得像同步</li>
<li>本质:await 时挂起方法,释放线程;操作完成后恢复执行</li>
</ul>
<div> </div>
<hr>
<div> </div>
<h1>二、Task 到底是什么?</h1>
<div> </div>
<h2>1. Task 不是线程</h2>
<div> </div>
<div>很多人误区:</div>
<div> </div>
<blockquote>
<div>“启动一个 Task 就开一个线程。”</div>
<div> </div>
</blockquote>
<div> </div>
<div>错。</div>
<div> </div>
<ul>
<li>Task 是操作的承诺(Promise)</li>
<li>它只表示 “这件事未来会完成”</li>
<li>不代表一定用新线程</li>
</ul>
<div> </div>
<h2>2. Task 分两类</h2>
<div> </div>
<h3>(1)CPU 密集型</h3>
<div> </div>
<div>真正开线程 / 线程池</div>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>Task.Run(() => {
// 计算密集逻辑
});
</code></pre>
</div>
<div> </div>
</div>
</div>
<h3>(2)IO 密集型</h3>
<div> </div>
<div>网络请求、文件读写、数据库、延时……
<div> </div>
不占线程!
<div> </div>
内核级异步,线程直接释放,等硬件中断回来。</div>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>await httpClient.GetAsync(url);
await File.ReadAllTextAsync(path);
await Task.Delay(1000);
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>3. Task 状态机</h2>
<div> </div>
<div>一个 Task 有:</div>
<div> </div>
<ul>
<li><code>RanToCompletion</code> 成功</li>
<li><code>Faulted</code> 异常</li>
<li><code>Canceled</code> 取消</li>
<li><code>IsCompleted</code> 是否完成</li>
</ul>
<div> </div>
<h2>4. Task 为什么能 “等待”?</h2>
<div> </div>
<div>因为它实现了
<div> </div>
<code>GetAwaiter()</code>
<div> </div>
只要一个对象有这个方法,就能被 await。</div>
<div> </div>
<hr>
<div> </div>
<h1>三、async/await 原理</h1>
<div> </div>
<div>编译器会把 async 方法编译成一个状态机类,结构类似:</div>
<div> </div>
<ol>
<li>走到 await</li>
<li>保存当前方法上下文(变量、位置)</li>
<li>挂起方法,返回调用方</li>
<li>线程释放,去干别的</li>
<li>异步操作完成</li>
<li>从线程池取线程,恢复上下文,继续执行后续代码</li>
</ol>
<div> </div>
<div>关键点:
<div> </div>
await 之后的代码,不一定在原来线程上执行!</div>
<div> </div>
<hr>
<div> </div>
<h1>四、async/await 用法</h1>
<div> </div>
<h2>1. 标准写法</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>async Task<int> GetDataAsync()
{
await Task.Delay(100);// 异步等待
return 100;
}
</code></pre>
</div>
<div> </div>
</div>
</div>
<div> </div>
<h2>2. 无返回值</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>async Task WorkAsync() { ... }
</code></pre>
</div>
<div> </div>
</div>
</div>
<div> </div>
<div>不要用 <code>async void</code>!除非是事件处理。</div>
<div> </div>
<h2>3. 等待多个任务</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>await Task.WhenAll(t1, t2, t3);
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>4. 任一完成就继续</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>await Task.WhenAny(t1, t2);
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>5. 同步等待(危险)</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>task.Wait();
var result = task.Result;
</code></pre>
</div>
<div> </div>
</div>
</div>
<div>这是死锁重灾区。</div>
<div> </div>
<hr>
<div> </div>
<h1>五、异步死锁 99% 场景:上下文争夺</h1>
<div> </div>
<h2>经典死锁代码(WinForm / WPF / ASP.NET(非 Core)必现)</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>// UI线程或ASP.NET主线程
void Button_Click()
{
var t = GetDataAsync();
t.Wait(); // 阻塞主线程
}
async Task<int> GetDataAsync()
{
await Task.Delay(100);// 想切回原上下文
return 1;
}
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>为什么死锁?</h2>
<div> </div>
<ol>
<li>主线程被 Wait () 阻塞</li>
<li>await 完成后,想回到主线程上下文继续执行</li>
<li>但主线程已经卡住,在等 Task 完成</li>
<li>互相等待 → 死锁</li>
</ol>
<div> </div>
<h2>根本原因</h2>
<div> </div>
<div>默认情况下:
<div> </div>
await 会尝试恢复到原 SynchronizationContext</div>
<div> </div>
<ul>
<li>UI:UI 线程</li>
<li>ASP.NET:请求上下文</li>
<li>Console / ASP.NET Core:无上下文,不会死锁</li>
</ul>
<div> </div>
<hr>
<div> </div>
<h1>六、解决死锁的方案</h1>
<div> </div>
<h2>1. 推荐全程 async/await,不阻塞</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>async void Button_Click()
{
await GetDataAsync();
}
</code></pre>
</div>
<div> </div>
</div>
</div>
<div> </div>
<h2>2. 必须同步等待时:</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>task.ConfigureAwait(false).GetAwaiter().GetResult();
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>3. 库代码加</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>await SomeTask().ConfigureAwait(false);
</code></pre>
</div>
<div> </div>
</div>
</div>
<div>含义:
<div> </div>
不需要恢复到原来的上下文,随便找个线程池线程继续。</div>
<div> </div>
<div>这是杜绝死锁的最关键习惯。</div>
<div> </div>
<hr>
<div> </div>
<h1>七、async/await 常见坑</h1>
<div> </div>
<h2>1. async void 灾难</h2>
<div> </div>
<div>除了事件,永远不要写 async void</div>
<div> </div>
<ul>
<li>异常抓不到</li>
<li>无法等待</li>
<li>无法取消</li>
<li>无法处理异常</li>
</ul>
<div> </div>
<h2>2. 忘记 await</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>DoWorkAsync(); // 直接调用,不等待
</code></pre>
</div>
<div> </div>
</div>
</div>
<div>变成 “火并忘”(fire and forget)
<div> </div>
异常直接吞,程序莫名崩。</div>
<div> </div>
<h2>3. 重复 await</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>var t = MethodAsync();
await t;
await t; // 无害,但多余
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>4. Task.Run 包裹本来就异步的方法</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>await Task.Run(async ()=> await httpClient.GetAsync(...));
</code></pre>
</div>
<div> </div>
</div>
</div>
<div>毫无意义,浪费线程。</div>
<div> </div>
<h2>5. 用 Task.Delay 做循环轮询</h2>
<div> </div>
<div>可以,但要加取消令牌。</div>
<div> </div>
<hr>
<div> </div>
<h1>八、Task 原理进阶:线程去哪儿了?</h1>
<div> </div>
<h2>IO 异步真正流程</h2>
<div> </div>
<ol>
<li>调用 <code>await ReadAsync</code></li>
<li>线程发出指令给操作系统</li>
<li>线程回到线程池</li>
<li>磁盘 / 网络完成,发中断</li>
<li>线程池取出一个线程</li>
<li>恢复 async 方法,继续执行</li>
</ol>
<div> </div>
<div>一句话:
<div> </div>
异步 IO 不阻塞线程,线程是被释放的,不是在等待。</div>
<div> </div>
<div>这就是高并发关键。</div>
<div> </div>
<hr>
<div> </div>
<h1>九、实践</h1>
<div> </div>
<h2>1. 方法名后缀 Async</h2>
<div> </div>
<div><code>GetDataAsync()</code> <code>SaveAsync()</code></div>
<div> </div>
<h2>2. 库代码一律</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>await xxx.ConfigureAwait(false);
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>3. 不使用 .Result/.Wait () /.WaitAll ()</h2>
<div> </div>
<div>除非你非常清楚上下文机制。</div>
<div> </div>
<h2>4. 尽量返回 Task,不要 async void</h2>
<div> </div>
<div>事件除外。</div>
<div> </div>
<h2>5. 异常统一捕获</h2>
<div> </div>
<div>await 内部异常会正常抛出,直接 try/catch 即可。</div>
<div> </div>
<h2>6. 用 CancellationToken 做取消</h2>
<div> </div>
<div>
<div dir="ltr">
<div>
<pre><code>await MethodAsync(cts.Token);
</code></pre>
</div>
<div> </div>
</div>
</div>
<h2>7. 不要在异步里锁(Monitor、lock)</h2>
<div> </div>
<div>极易死锁。</div>
<div> </div>
<h2>8. ASP.NET Core 全程异步</h2>
<div> </div>
<div>从控制器 → 服务 → 数据库全异步,吞吐量提升巨大。</div>
<div> </div>
<hr>
<div> </div>
<h1>十、总结</h1>
<div> </div>
<ul>
<li>Task 是操作的承诺,不是线程</li>
<li>async/await 是状态机语法糖,实现挂起与恢复</li>
<li>await 不阻塞线程,释放线程 → 完成后恢复</li>
<li>死锁根源:同步阻塞(Wait/Result)+ 上下文恢复</li>
<li>防死锁:全程异步 + ConfigureAwait (false)</li>
<li>异步 IO 高并发关键:不占线程等待</li>
</ul><br><br>
来源:https://www.cnblogs.com/chuansheng/p/19908907
頁:
[1]