【.NET并发编程 - 03】 Task API 完全指南:方法与属性的实战应用
<h1 id="03-task-api-完全指南方法与属性的实战应用">03. Task API 完全指南:方法与属性的实战应用</h1><blockquote>
<p><strong>本章 GitHub 仓库</strong>:csharp-concurrency-cookbook ⭐</p>
<p>欢迎 Star 和 Fork!所有代码示例都可以在仓库中找到并运行。</p>
</blockquote>
<hr>
<h2 id="-本章导读">🎯 本章导读</h2>
<blockquote>
<p>📌 <strong>本文目标</strong>:系统性掌握 Task 类的核心 API,为后续深入学习 async/await 打下坚实基础。</p>
</blockquote>
<p>在上一篇文章中,我们了解了 Task 是基于 ThreadPool 的抽象,是现代 .NET 并发编程的核心。本文将深入探讨 Task 类提供的各种方法和属性:</p>
<ul>
<li><strong>创建任务</strong>:如何创建并启动一个 Task?</li>
<li><strong>等待任务</strong>:如何等待任务完成并获取结果?</li>
<li><strong>组合任务</strong>:如何组合多个任务?</li>
<li><strong>任务状态</strong>:如何查询任务的执行状态?</li>
<li><strong>常见陷阱</strong>:哪些用法容易出错?</li>
</ul>
<blockquote>
<p>⚠️ <strong>重要提示</strong>:本文聚焦 Task API 本身,关于 async/await 的深入讨论将在下一章展开。</p>
</blockquote>
<hr>
<h2 id="1️⃣-创建任务三种方式的选择">1️⃣ 创建任务:三种方式的选择</h2>
<h3 id="11-taskrun最常用的方式">1.1 Task.Run:最常用的方式</h3>
<p><strong>作用说明</strong>:<code>Task.Run()</code> 将一个委托(Lambda 表达式或方法)排队到 ThreadPool,并立即返回一个表示该操作的 Task 对象。任务会在线程池的某个工作线程上异步执行。它是创建 CPU 密集型任务的推荐方式,自动处理任务的启动和调度。</p>
<p><strong>适用场景</strong>:将 CPU 密集型工作放到线程池执行。</p>
<pre><code class="language-csharp">// 最简单的方式:执行一个操作
Task task = Task.Run(() =>
{
Console.WriteLine($"在线程 {Thread.CurrentThread.ManagedThreadId} 上执行");
Thread.Sleep(1000);
});
// 带返回值的版本
Task<int> resultTask = Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
});
int result = resultTask.Result; // 阻塞等待结果
Console.WriteLine($"结果: {result}");
</code></pre>
<p><strong>核心特点</strong>:</p>
<ul>
<li>✅ 立即启动,无需手动调用 <code>Start()</code></li>
<li>✅ 自动使用 ThreadPool 线程池</li>
<li>✅ 代码简洁,是最推荐的方式</li>
</ul>
<hr>
<h3 id="12-taskfactorystartnew高级控制">1.2 Task.Factory.StartNew:高级控制</h3>
<p><strong>作用说明</strong>:<code>Task.Factory.StartNew()</code> 提供了比 <code>Task.Run</code> 更多的控制选项,允许指定 <code>TaskCreationOptions</code>(如长时间运行)、<code>CancellationToken</code>、以及自定义的 <code>TaskScheduler</code>。它也会立即启动任务,但默认行为与 <code>Task.Run</code> 有细微差异(如调度器的选择)。</p>
<p><strong>适用场景</strong>:需要更精细的控制(如长时间运行任务、自定义调度器)。</p>
<pre><code class="language-csharp">// 普通用法(不推荐,应该用 Task.Run)
Task task1 = Task.Factory.StartNew(() =>
{
Console.WriteLine("普通任务");
});
// 标记为长时间运行任务(会创建专用线程,不占用线程池)
Task task2 = Task.Factory.StartNew(() =>
{
Console.WriteLine("长时间运行的任务");
Thread.Sleep(10000);
}, TaskCreationOptions.LongRunning);
// 使用自定义调度器
Task task3 = Task.Factory.StartNew(() =>
{
Console.WriteLine("自定义调度器");
}, CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
</code></pre>
<p><strong>⚠️ 关键差异</strong>:</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>Task.Run</th>
<th>Task.Factory.StartNew</th>
</tr>
</thead>
<tbody>
<tr>
<td>默认调度器</td>
<td>TaskScheduler.Default</td>
<td>TaskScheduler.Current</td>
</tr>
<tr>
<td>嵌套任务</td>
<td>自动展开</td>
<td>需要手动展开</td>
</tr>
<tr>
<td>建议用途</td>
<td>日常使用</td>
<td>高级场景</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="13-new-task手动控制启动">1.3 new Task():手动控制启动</h3>
<p><strong>作用说明</strong>:使用 <code>new Task()</code> 创建任务时,任务不会自动启动,需要手动调用 <code>Start()</code> 方法。这允许在创建和启动之间进行额外的配置或等待特定时机。任务创建后处于 <code>Created</code> 状态,调用 <code>Start()</code> 后才会排队到线程池执行。</p>
<p><strong>适用场景</strong>:需要延迟启动或特殊控制流程。</p>
<pre><code class="language-csharp">// 创建但不启动
Task task = new Task(() =>
{
Console.WriteLine("手动启动的任务");
});
Console.WriteLine($"任务状态: {task.Status}"); // Created
// 手动启动
task.Start();
// 等待完成
task.Wait();
</code></pre>
<p><strong>❌ 常见错误</strong>:</p>
<pre><code class="language-csharp">// 错误:忘记调用 Start()
var task = new Task(() => Console.WriteLine("Hello"));
// task 永远不会执行!
</code></pre>
<p><strong>✅ 正确做法</strong>:<br>
99% 的情况下应该使用 <code>Task.Run</code>,除非你明确需要延迟启动。</p>
<hr>
<h3 id="14-创建已完成的任务">1.4 创建已完成的任务</h3>
<p><strong>作用说明</strong>:这些静态方法用于创建已经处于完成状态的 Task,无需实际执行任何异步操作。它们适用于同步路径的优化(避免不必要的异步开销)、测试场景、或需要返回固定结果的场景。</p>
<p>对于某些场景(如缓存、测试、优化),我们需要直接创建已完成的任务:</p>
<pre><code class="language-csharp">// Task.FromResult - 返回已成功完成的任务(带返回值)
Task<int> completedTask = Task.FromResult(42);
Console.WriteLine($"立即可用: {completedTask.Result}"); // 不会阻塞,立即返回
// Task.CompletedTask - 返回已成功完成的任务(无返回值)
Task emptyTask = Task.CompletedTask;
// Task.FromCanceled - 返回已取消的任务
CancellationTokenSource cts = new CancellationTokenSource();
cts.Cancel();
Task canceledTask = Task.FromCanceled(cts.Token);
// Task.FromException - 返回已失败的任务
Task faultedTask = Task.FromException(new InvalidOperationException("出错了"));
</code></pre>
<p><strong>实战应用</strong>:接口实现的优化</p>
<pre><code class="language-csharp">public interface IDataService
{
Task<string> GetDataAsync();
}
// 缓存实现:数据已在内存中,无需真正的异步操作
public class CachedDataService : IDataService
{
private string _cachedData = "缓存的数据";
public Task<string> GetDataAsync()
{
// 避免不必要的异步开销
return Task.FromResult(_cachedData);
}
}
</code></pre>
<hr>
<h2 id="2️⃣-等待任务同步等待的陷阱">2️⃣ 等待任务:同步等待的陷阱</h2>
<h3 id="21-wait阻塞等待">2.1 Wait():阻塞等待</h3>
<p><strong>作用说明</strong>:<code>Wait()</code> 方法会阻塞当前线程,直到任务完成才返回。如果任务已完成,则立即返回;如果任务尚未完成,当前线程会被挂起,进入等待状态,直到任务执行完毕。这是一个<strong>同步阻塞</strong>方法,会占用调用线程,直到任务结束。</p>
<pre><code class="language-csharp">Task task = Task.Run(() =>
{
Thread.Sleep(2000);
Console.WriteLine("任务完成");
});
// 阻塞当前线程,直到任务完成
task.Wait();
Console.WriteLine("继续执行");
</code></pre>
<p><strong>⚠️ 性能陷阱:为什么 .Result 和 .Wait() 会浪费线程资源?</strong></p>
<pre><code class="language-csharp">// ❌ 错误:在 ASP.NET Core 中阻塞等待
public IActionResult GetData()
{
var data = GetDataAsync().Result; // 浪费线程资源!
return Ok(data);
}
// ✅ 正确:使用 async/await(第4章详解)
public async Task<IActionResult> GetData()
{
var data = await GetDataAsync();
return Ok(data);
}
</code></pre>
<h4 id="-内部实现机制为什么会阻塞线程">📌 <strong>内部实现机制:为什么会阻塞线程?</strong></h4>
<p>要理解为什么 <code>.Result</code> 和 <code>.Wait()</code> 会浪费线程资源,我们需要深入了解它们的<strong>内部实现机制</strong>。</p>
<p><strong>1. Wait() 的内部实现原理</strong></p>
<p><code>Wait()</code> 方法内部使用 <strong>ManualResetEventSlim</strong> 作为同步原语来实现阻塞。简化的内部流程如下:</p>
<div class="mermaid">flowchart TD
Start([调用 task.Wait]) --> CheckStatus{检查任务状态}
CheckStatus -->|任务已完成| ReturnFast[立即返回<br/>不阻塞]
CheckStatus -->|任务未完成| SpinWait[阶段1: SpinWait 自旋等待<br/>━━━━━━━━━━━━━━━━<br/>• 用户态循环检查任务状态<br/>• 持续约 10-30 次循环<br/>• 避免昂贵的内核态切换<br/>• 线程状态: Running 占用CPU]
SpinWait --> SpinCheck{自旋期间<br/>任务完成?}
SpinCheck -->|是| ReturnSpin[返回 幸运!]
SpinCheck -->|否| BlockWait[阶段2: ManualResetEventSlim.Wait<br/>━━━━━━━━━━━━━━━━━━━━━━<br/>内核阻塞]
BlockWait --> ThreadBlock[线程状态转换<br/>━━━━━━━━━━<br/>Running → WaitSleepJoin<br/>• OS调度器将线程移出运行队列<br/>• 线程进入内核态等待队列<br/>• 不再消耗CPU但占用线程池槽位]
ThreadBlock --> Suspended[线程被挂起 等待信号<br/>━━━━━━━━━━━━━━<br/>• 线程对象在内存 ~1MB栈空间<br/>• 占用ThreadPool槽位<br/>• OS不分配CPU时间片]
Suspended --> TaskComplete[任务完成事件<br/>━━━━━━━━━]
TaskComplete --> SetResult
SetResult --> SignalEvent
SignalEvent --> WakeUp[操作系统唤醒线程]
WakeUp --> ThreadReady[线程状态转换<br/>━━━━━━━━━━<br/>WaitSleepJoin → Ready → Running]
ThreadReady --> ReturnWait
style Start fill:#bbdefb,color:#1565c0,stroke:#1976d2,stroke-width:2px
style ReturnFast fill:#c8e6c9,color:#2e7d32,stroke:#388e3c,stroke-width:2px
style ReturnSpin fill:#c8e6c9,color:#2e7d32,stroke:#388e3c,stroke-width:2px
style ReturnWait fill:#c8e6c9,color:#2e7d32,stroke:#388e3c,stroke-width:2px
style SpinWait fill:#fff9c4,color:#f57f17,stroke:#f9a825,stroke-width:2px
style BlockWait fill:#ffccbc,color:#d84315,stroke:#e64a19,stroke-width:2px
style ThreadBlock fill:#ffccbc,color:#d84315,stroke:#e64a19,stroke-width:2px
style Suspended fill:#ffccbc,color:#d84315,stroke:#e64a19,stroke-width:2px
style SetResult fill:#e1bee7,color:#6a1b9a,stroke:#7b1fa2,stroke-width:2px
style SignalEvent fill:#e1bee7,color:#6a1b9a,stroke:#7b1fa2,stroke-width:2px
style WakeUp fill:#e1bee7,color:#6a1b9a,stroke:#7b1fa2,stroke-width:2px
</div><p><strong>关键实现细节</strong>:</p>
<ul>
<li><strong>ManualResetEventSlim</strong>:.NET 的轻量级同步原语,内部封装了 Win32 的 <code>WaitHandle</code>(在 Windows 上是 <code>CreateEvent</code> / <code>WaitForSingleObject</code>)</li>
<li><strong>两阶段等待策略</strong>:
<ul>
<li><strong>自旋(SpinWait)</strong>:短时间内在用户态循环检查,避免线程切换开销(适合非常快完成的任务)</li>
<li><strong>阻塞(Blocking)</strong>:如果自旋超时,则调用内核同步原语,真正挂起线程(节省 CPU,但线程仍被占用)</li>
</ul>
</li>
</ul>
<p><strong>2. .Result 属性的内部实现</strong></p>
<p><code>.Result</code> 属性的实现更简单,它本质上是:</p>
<pre><code class="language-csharp">// Task<T>.Result 的简化实现(伪代码)
public T Result
{
get
{
// 1. 如果任务未完成,调用 Wait() 阻塞
if (!IsCompleted)
{
InternalWait(Timeout.Infinite, default);
}
// 2. 如果任务出错,重新抛出异常(包装在 AggregateException 中)
if (IsFaulted)
{
throw new AggregateException(Exception);
}
// 3. 返回结果
return _result;
}
}
</code></pre>
<p><strong>可以看到,<code>.Result</code> 内部直接调用 <code>Wait()</code>,因此阻塞机制完全相同。</strong></p>
<h4 id="-线程状态转换与资源占用分析">📊 <strong>线程状态转换与资源占用分析</strong></h4>
<p>在第 02 章我们学到,ThreadPool 是有限的线程资源。现在让我们结合内部实现来分析 <strong>Wait() 造成的资源浪费</strong>:</p>
<h4 id="-aspnet-core-中的资源浪费示例">📊 <strong>ASP.NET Core 中的资源浪费示例</strong></h4>
<div class="mermaid">sequenceDiagram
participant Client as 客户端
participant TP as ThreadPool
participant RT as 请求线程 #1
participant IO as I/O 完成端口<br/>(IOCP)
participant WT as 工作线程 #2
participant DB as 数据库
Note over Client,DB: T0: 请求到达
Client->>TP: HTTP 请求
TP->>RT: 分配线程
activate RT
Note right of RT: 状态: Running<br/>槽位: 1/64
Note over Client,DB: T1: 调用 GetDataAsync().Result
RT->>RT: 开始 SpinWait
Note right of RT: 用户态自旋<br/>占用 CPU
Note over Client,DB: T2: SpinWait 超时,进入内核阻塞
RT->>RT: ManualResetEventSlim.Wait()
Note right of RT: ⚠️ 状态: Running → WaitSleepJoin<br/>━━━━━━━━━━━━━━<br/>资源占用:<br/>• ThreadPool 槽位: 1/64<br/>• 栈内存: ~1MB<br/>• 内核句柄: 1 个
RT->>IO: 提交异步 I/O
Note right of IO: 不占用线程
Note over Client,DB: T2~T4: 请求线程被阻塞(浪费!)
Note right of RT: ⚠️ 线程什么都不做<br/>但占用 ThreadPool 槽位
Note over Client,DB: T3: I/O 操作完成
DB-->>IO: 返回数据
IO->>TP: IOCP 完成通知
TP->>WT: 分配工作线程
activate WT
WT->>RT: ManualResetEventSlim.Set()
Note right of RT: 发出信号
Note over Client,DB: T4: 唤醒被阻塞的线程
Note right of RT: 状态: WaitSleepJoin<br/>→ Ready → Running
RT->>RT: Wait() 返回
Note over Client,DB: T5: 返回 HTTP 响应
RT->>Client: 响应数据
deactivate RT
RT->>TP: 归还线程
deactivate WT
Note over Client,DB: 【问题分析】<br/>━━━━━━━━━━━━━━━━━━━━━━━<br/>• T2~T4 期间请求线程空闲但占用槽位<br/>• 100 并发 = 100 个被阻塞的线程<br/>• ThreadPool 耗尽 → 新请求排队<br/>• 高延迟、低吞吐量、超时风险
</div><h4 id="-性能影响分析">🎯 <strong>性能影响分析</strong></h4>
<p><strong>资源占用对比</strong>:</p>
<table>
<thead>
<tr>
<th>资源类型</th>
<th>单个阻塞线程的占用</th>
<th>100 个并发请求的占用</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ThreadPool 槽位</strong></td>
<td>1 个(持续占用)</td>
<td>100 个(可能耗尽 ThreadPool)</td>
</tr>
<tr>
<td><strong>线程栈内存</strong></td>
<td>~1MB</td>
<td>~100MB</td>
</tr>
<tr>
<td><strong>内核对象</strong></td>
<td>1 个 HANDLE 句柄</td>
<td>100 个 HANDLE</td>
</tr>
<tr>
<td><strong>CPU 利用率</strong></td>
<td>0%(空闲但不释放)</td>
<td>0%(100 个线程都在等待)</td>
</tr>
</tbody>
</table>
<p><strong>真实世界的影响</strong>:</p>
<p>在 ASP.NET Core 应用中,假设每个数据库查询耗时 100ms:</p>
<ul>
<li><strong>ThreadPool 默认最大线程数</strong>:通常为 CPU 核心数的倍数(如 64 个)</li>
<li><strong>理论吞吐量</strong>:64 个线程 ÷ 0.1秒 = <strong>640 请求/秒</strong></li>
<li><strong>实际问题</strong>:
<ul>
<li>当并发请求超过 64 个时,新请求必须<strong>排队等待</strong>线程释放</li>
<li>排队导致延迟增加,用户体验下降</li>
<li>严重时可能导致请求超时、服务不可用</li>
</ul>
</li>
</ul>
<p><strong>为什么会造成性能崩溃?</strong></p>
<ol>
<li><strong>线程饥饿</strong>:ThreadPool 的线程被大量阻塞操作占据,无法处理新请求</li>
<li><strong>内存浪费</strong>:每个阻塞线程占用 ~1MB 栈空间,但完全空闲</li>
<li><strong>延迟雪崩</strong>:排队等待的请求越来越多,延迟指数级增长</li>
<li><strong>资源死锁风险</strong>:在复杂场景下,可能导致所有线程互相等待</li>
</ol>
<blockquote>
<p><strong>💡 提示</strong>:这就是为什么在高并发场景下,<strong>绝对不能</strong>使用 <code>.Result</code> 或 <code>.Wait()</code> 阻塞等待异步操作。正确的做法是使用 <code>async/await</code>,这将在第 04 章详细讲解。</p>
</blockquote>
<blockquote>
<p><strong>📚 扩展阅读</strong>:关于 ThreadPool 工作原理和 I/O 完成端口(IOCP)机制,请参考第 02 章《深入理解 ThreadPool》。</p>
</blockquote>
<hr>
<h3 id="22-waittimeout带超时的等待">2.2 Wait(timeout):带超时的等待</h3>
<p><strong>作用说明</strong>:<code>Wait(timeout)</code> 是 <code>Wait()</code> 的超时版本,它会阻塞当前线程,但最多等待指定的时间(毫秒)。如果任务在超时时间内完成,方法返回 <code>true</code>;如果超时了任务仍未完成,方法返回 <code>false</code>,但<strong>任务会继续在后台执行</strong>,不会被取消或中止。</p>
<pre><code class="language-csharp">Task longTask = Task.Run(() =>
{
Thread.Sleep(5000);
});
// 等待最多 2 秒
bool completed = longTask.Wait(2000);
if (completed)
{
Console.WriteLine("任务已完成");
}
else
{
Console.WriteLine("等待超时");
// 注意:任务仍在后台继续执行
}
</code></pre>
<hr>
<h3 id="23-taskwaitall等待所有任务">2.3 Task.WaitAll:等待所有任务</h3>
<p><strong>作用说明</strong>:<code>Task.WaitAll()</code> 会阻塞当前线程,直到<strong>所有</strong>传入的任务都完成(包括成功、失败或取消)才返回。多个任务是<strong>并发执行</strong>的,总耗时等于耗时最长的那个任务。如果任何一个任务抛出异常,<code>WaitAll</code> 会等待所有任务完成后,将所有异常包装在 <code>AggregateException</code> 中抛出。</p>
<pre><code class="language-csharp">Task task1 = Task.Run(() => Thread.Sleep(1000));
Task task2 = Task.Run(() => Thread.Sleep(2000));
Task task3 = Task.Run(() => Thread.Sleep(1500));
// 阻塞直到所有任务完成(取最长时间)
Task.WaitAll(task1, task2, task3);
Console.WriteLine("所有任务完成"); // 大约 2 秒后输出(不是 4.5 秒)
</code></pre>
<p><strong>带超时的版本</strong>:</p>
<pre><code class="language-csharp">// 最多等待 3 秒,如果超时返回 false,但所有任务会继续执行
bool allCompleted = Task.WaitAll(new[] { task1, task2, task3 }, 3000);
if (!allCompleted)
{
Console.WriteLine("等待超时,但任务仍在后台执行");
}
</code></pre>
<hr>
<h3 id="24-taskwaitany等待任意一个">2.4 Task.WaitAny:等待任意一个</h3>
<p><strong>作用说明</strong>:<code>Task.WaitAny()</code> 会阻塞当前线程,直到传入的任务中<strong>任意一个</strong>完成(无论成功、失败还是取消)就立即返回。返回值是<strong>第一个完成的任务在数组中的索引</strong>(从 0 开始)。<strong>重要</strong>:方法返回后,其他未完成的任务<strong>不会被取消或中止</strong>,它们会继续在后台执行,直到完成或程序退出。</p>
<pre><code class="language-csharp">Task<int> task1 = Task.Run(async () =>
{
await Task.Delay(2000);
Console.WriteLine("任务 1 完成");
return 1;
});
Task<int> task2 = Task.Run(async () =>
{
await Task.Delay(1000);
Console.WriteLine("任务 2 完成");
return 2;
});
Task<int> task3 = Task.Run(async () =>
{
await Task.Delay(1500);
Console.WriteLine("任务 3 完成");
return 3;
});
// 返回第一个完成的任务的索引
int index = Task.WaitAny(task1, task2, task3);
Console.WriteLine($"任务 {index + 1} 先完成"); // 任务 2 先完成(1 秒后)
// 注意:此时任务 1 和任务 3 仍在后台执行
// 它们会在 1.5 秒和 2 秒后继续输出 "任务 3 完成" 和 "任务 1 完成"
</code></pre>
<p><strong>获取第一个完成的任务的结果</strong>:</p>
<pre><code class="language-csharp">Task<int>[] tasks = new[] { task1, task2, task3 };
int index = Task.WaitAny(tasks);
Task<int> firstCompleted = tasks;
int result = firstCompleted.Result; // 安全访问,因为任务已完成
Console.WriteLine($"第一个结果: {result}");
</code></pre>
<p><strong>实战应用:超时模式</strong></p>
<pre><code class="language-csharp">Task<string> dataTask = GetDataFromSlowServiceAsync();
Task timeoutTask = Task.Delay(5000); // 5 秒超时
int index = Task.WaitAny(dataTask, timeoutTask);
if (index == 0)
{
Console.WriteLine($"获取到数据: {((Task<string>)dataTask).Result}");
}
else
{
Console.WriteLine("超时!");
}
</code></pre>
<hr>
<h3 id="25-getawaiter等待器的底层机制">2.5 GetAwaiter:等待器的底层机制</h3>
<p><strong>作用说明</strong>:<code>GetAwaiter()</code> 返回一个 <code>TaskAwaiter</code> 或 <code>TaskAwaiter<T></code>,这是 <code>await</code> 关键字的底层实现机制。通常不需要直接使用,但在特殊场景下(如同步方法中调用异步方法)会用到。</p>
<pre><code class="language-csharp">// 等价于 await task
TaskAwaiter<int> awaiter = task.GetAwaiter();
int result = awaiter.GetResult();
// 等价于 await task.ConfigureAwait(false)
ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter awaiter =
task.ConfigureAwait(false).GetAwaiter();
int result = awaiter.GetResult();
</code></pre>
<p><strong>与 .Result 的区别</strong>:</p>
<table>
<thead>
<tr>
<th>方式</th>
<th>异常处理</th>
<th>死锁风险</th>
<th>推荐度</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><code>.Result</code></strong></td>
<td>包装为 <code>AggregateException</code></td>
<td>高(在 UI 线程)</td>
<td>❌ 不推荐</td>
</tr>
<tr>
<td><strong><code>.GetAwaiter().GetResult()</code></strong></td>
<td>直接抛出原始异常</td>
<td>高(在 UI 线程)</td>
<td>⚠️ 特殊场景</td>
</tr>
<tr>
<td><strong><code>.ConfigureAwait(false).GetAwaiter().GetResult()</code></strong></td>
<td>直接抛出原始异常</td>
<td>较低(不捕获上下文)</td>
<td>⚙️ 相对安全</td>
</tr>
<tr>
<td><strong><code>await</code></strong></td>
<td>直接抛出原始异常</td>
<td>无</td>
<td>✅ <strong>强烈推荐</strong></td>
</tr>
</tbody>
</table>
<p><strong>示例:异常处理的区别</strong></p>
<pre><code class="language-csharp">async Task ThrowExceptionAsync()
{
await Task.Delay(100);
throw new InvalidOperationException("Something went wrong");
}
// ❌ 使用 .Result:捕获 AggregateException
try
{
ThrowExceptionAsync().Result;
}
catch (AggregateException ex)
{
Console.WriteLine($"外层异常: {ex.GetType().Name}");
Console.WriteLine($"内层异常: {ex.InnerException?.GetType().Name}");
// 输出:
// 外层异常: AggregateException
// 内层异常: InvalidOperationException
}
// ✅ 使用 GetAwaiter().GetResult():直接捕获原始异常
try
{
ThrowExceptionAsync().GetAwaiter().GetResult();
}
catch (InvalidOperationException ex)// 直接捕获原始异常!
{
Console.WriteLine($"异常: {ex.GetType().Name}");
// 输出:
// 异常: InvalidOperationException
}
</code></pre>
<p><strong>⚠️ 重要警告</strong>:</p>
<ul>
<li><code>GetAwaiter().GetResult()</code> 仍然是<strong>阻塞调用</strong>(会占用线程资源)</li>
<li>在 UI 线程使用仍然有<strong>死锁风险</strong></li>
<li><strong>最佳实践</strong>:将整个调用链改为 <code>async/await</code></li>
</ul>
<blockquote>
<p><strong>💡 何时可以使用</strong>:</p>
<ul>
<li>遗留代码迁移(无法改为 async)</li>
<li><code>Main</code> 方法(.NET Framework,.NET 6+ 可以用 top-level statements)</li>
<li>同步接口的实现(如 <code>IDisposable.Dispose</code>,但更推荐 <code>IAsyncDisposable</code>)</li>
</ul>
<p><strong>📚 详细原理</strong>:关于 <code>TaskAwaiter</code>、同步上下文、死锁机制的详细讲解,会在后面章节进行。</p>
</blockquote>
<hr>
<h3 id="26-configureawait配置等待上下文">2.6 ConfigureAwait:配置等待上下文</h3>
<p><strong>作用说明</strong>:<code>ConfigureAwait(bool continueOnCapturedContext)</code> 控制 <code>await</code> 之后的代码是否要返回原始上下文(如 UI 线程)执行。这是 Task API 的一个重要方法,但其深入原理涉及 <code>SynchronizationContext</code>,将在第 05 章详细讲解。</p>
<pre><code class="language-csharp">// 基本用法
await SomeTaskAsync().ConfigureAwait(false);// 不捕获上下文
await SomeTaskAsync().ConfigureAwait(true); // 捕获上下文(默认)
</code></pre>
<p><strong>简要说明</strong>:</p>
<ul>
<li>
<p><strong>ConfigureAwait(false)</strong>:</p>
<ul>
<li>不返回原始上下文(如 UI 线程)</li>
<li>性能更高(避免线程切换)</li>
<li><strong>库代码推荐</strong>使用此选项</li>
</ul>
</li>
<li>
<p><strong>ConfigureAwait(true)</strong>(默认):</p>
<ul>
<li>返回原始上下文执行</li>
<li><strong>UI 代码必须</strong>使用此选项(或不写,默认就是 true)</li>
<li>例如:<code>await</code> 后需要更新 UI 控件</li>
</ul>
</li>
</ul>
<p><strong>示例对比</strong>:</p>
<pre><code class="language-csharp">// UI 代码(WPF/WinForms)
private async void Button_Click(object sender, EventArgs e)
{
// ✅ 正确:需要回到 UI 线程更新控件
string data = await GetDataAsync();// 默认 ConfigureAwait(true)
textBox.Text = data;// 安全:在 UI 线程执行
}
// 库代码
public async Task<string> GetDataAsync()
{
// ✅ 正确:库代码不需要特定上下文
var response = await httpClient.GetAsync(url).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// 性能更好,避免不必要的上下文切换
}
</code></pre>
<hr>
<h2 id="3️⃣-组合任务异步编程的核心">3️⃣ 组合任务:异步编程的核心</h2>
<h3 id="31-taskwhenall并发执行多个任务">3.1 Task.WhenAll:并发执行多个任务</h3>
<p><strong>作用说明</strong>:<code>Task.WhenAll()</code> 返回一个新的 Task,该任务会在<strong>所有</strong>传入的任务都完成后才完成。与 <code>WaitAll</code> 不同,<code>WhenAll</code> 是<strong>异步</strong>的,使用 <code>await</code> 等待时不会阻塞线程。如果传入的是 <code>Task<T>[]</code>,则返回 <code>Task<T[]></code>,包含所有任务的结果。如果任何任务失败,会在 <code>await</code> 时抛出第一个异常(完整异常信息在 <code>task.Exception</code> 中)。</p>
<p><strong>场景</strong>:同时调用多个独立的 API。</p>
<pre><code class="language-csharp">async Task<string> GetUserAsync(int id)
{
await Task.Delay(1000);
return $"User{id}";
}
async Task<string> GetOrderAsync(int id)
{
await Task.Delay(1500);
return $"Order{id}";
}
// 串行执行:总耗时 2.5 秒
var user = await GetUserAsync(1);
var order = await GetOrderAsync(1);
// ✅ 并发执行:总耗时约 1.5 秒(取最长任务的时间)
Task<string> userTask = GetUserAsync(1);
Task<string> orderTask = GetOrderAsync(1);
string[] results = await Task.WhenAll(userTask, orderTask);
Console.WriteLine($"{results}, {results}");
</code></pre>
<p><strong>处理不同类型的返回值</strong>:</p>
<pre><code class="language-csharp">var userTask = GetUserAsync(1); // Task<string>
var countTask = GetUserCountAsync(); // Task<int>
// 等待所有任务完成(但无法直接获取结果数组,因为类型不同)
await Task.WhenAll(userTask, countTask);
// 任务完成后,可以安全访问 Result(不会阻塞)
string user = userTask.Result;
int count = countTask.Result;
</code></pre>
<hr>
<h3 id="32-taskwhenany响应最快的结果">3.2 Task.WhenAny:响应最快的结果</h3>
<p><strong>作用说明</strong>:<code>Task.WhenAny()</code> 返回一个新的 Task,该任务会在传入的任务中<strong>任意一个</strong>完成时就立即完成。返回值类型是 <code>Task<Task></code> 或 <code>Task<Task<T>></code>,即"完成的那个任务本身"。与 <code>WaitAny</code> 不同,<code>WhenAny</code> 是<strong>异步</strong>的,不会阻塞线程。<strong>重要</strong>:方法返回后,其他未完成的任务<strong>不会被取消</strong>,它们会继续执行,除非你手动取消。</p>
<p><strong>场景 1:超时控制</strong></p>
<pre><code class="language-csharp">async Task<string> GetDataWithTimeoutAsync()
{
Task<string> dataTask = GetDataFromSlowServiceAsync();
Task delayTask = Task.Delay(3000); // 3 秒超时
Task completedTask = await Task.WhenAny(dataTask, delayTask);
if (completedTask == dataTask)
{
return await dataTask; // 数据任务先完成
}
else
{
throw new TimeoutException("请求超时");
// 注意:dataTask 仍在后台执行,未被取消
}
}
</code></pre>
<p><strong>场景 2:多数据源竞速</strong></p>
<pre><code class="language-csharp">async Task<string> GetFastestDataAsync()
{
var source1 = GetDataFromSource1Async();
var source2 = GetDataFromSource2Async();
var source3 = GetDataFromSource3Async();
// 哪个先返回用哪个
Task<string> winner = await Task.WhenAny(source1, source2, source3);
return await winner; // 获取最快的结果
// 其他两个数据源的任务会继续执行,直到完成或程序退出
}
</code></pre>
<hr>
<h3 id="33-taskwheneach逐个处理完成的任务net-9">3.3 Task.WhenEach:逐个处理完成的任务(.NET 9+)</h3>
<blockquote>
<p>🆕 这是 .NET 9 新增的 API,用于按完成顺序处理任务。</p>
</blockquote>
<p><strong>作用说明</strong>:<code>Task.WhenEach()</code> 返回一个 <code>IAsyncEnumerable<Task<T>></code>,它会按照任务<strong>完成的顺序</strong>(而非创建顺序)逐个返回已完成的任务。这允许你在任务完成时立即处理它,而不是像 <code>WhenAll</code> 那样等待所有任务完成后一次性处理。所有任务仍然是<strong>并发执行</strong>的,只是处理顺序是按完成时间排序的。</p>
<pre><code class="language-csharp">#if NET9_0_OR_GREATER
async Task ProcessTasksAsTheyCompleteAsync()
{
Task<int>[] tasks = new[]
{
Task.Run(async () => { await Task.Delay(2000); return 1; }),
Task.Run(async () => { await Task.Delay(500); return 2; }),
Task.Run(async () => { await Task.Delay(1000); return 3; })
};
// 按完成顺序处理(不是创建顺序)
await foreach (var completedTask in Task.WhenEach(tasks))
{
int result = await completedTask;
Console.WriteLine($"任务完成,结果: {result}");
}
// 输出顺序: 2(500ms后), 3(1000ms后), 1(2000ms后)
}
#endif
</code></pre>
<p><strong>对比 Task.WhenAll</strong>:</p>
<ul>
<li><code>WhenAll</code>:等待所有任务完成后一次性处理,总耗时 = 最长任务的时间</li>
<li><code>WhenEach</code>:每完成一个立即处理,可以更早地开始后续操作,提升用户体验</li>
</ul>
<hr>
<h2 id="4️⃣-任务延续continuewith-vs-await">4️⃣ 任务延续:ContinueWith vs await</h2>
<h3 id="41-continuewith传统方式">4.1 ContinueWith:传统方式</h3>
<p><strong>作用说明</strong>:<code>ContinueWith()</code> 用于在任务完成后执行后续操作(延续任务)。它接收一个委托,该委托的参数是前一个任务本身(antecedent),可以通过它获取结果、状态或异常。延续任务会在前一个任务完成后立即执行,可以在同一线程或不同线程上执行(取决于 <code>TaskScheduler</code>)。</p>
<pre><code class="language-csharp">Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
})
.ContinueWith(antecedent =>
{
int result = antecedent.Result;
Console.WriteLine($"结果: {result}");
});
</code></pre>
<p><strong>处理异常的复杂性</strong>:</p>
<pre><code class="language-csharp">Task.Run(() =>
{
throw new InvalidOperationException("错误");
})
.ContinueWith(antecedent =>
{
if (antecedent.IsFaulted)
{
Console.WriteLine($"异常: {antecedent.Exception?.Message}");
}
else if (antecedent.IsCanceled)
{
Console.WriteLine("任务被取消");
}
else
{
Console.WriteLine($"结果: {antecedent.Result}");
}
});
</code></pre>
<hr>
<h3 id="42-await现代推荐方式">4.2 await:现代推荐方式</h3>
<p><strong>作用说明</strong>:<code>await</code> 关键字会异步等待任务完成,然后直接返回结果(对于 <code>Task<T></code>)或继续执行(对于 <code>Task</code>)。它使异步代码看起来像同步代码,异常可以通过标准的 <code>try-catch</code> 捕获。编译器会将其转换为状态机,自动处理线程调度和同步上下文。</p>
<pre><code class="language-csharp">try
{
int result = await Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
});
Console.WriteLine($"结果: {result}");
}
catch (Exception ex)
{
Console.WriteLine($"异常: {ex.Message}");
}
</code></pre>
<p><strong>对比总结</strong>:</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>ContinueWith</th>
<th>await</th>
</tr>
</thead>
<tbody>
<tr>
<td>代码可读性</td>
<td>❌ 嵌套回调</td>
<td>✅ 线性代码</td>
</tr>
<tr>
<td>异常处理</td>
<td>❌ 复杂</td>
<td>✅ try-catch</td>
</tr>
<tr>
<td>同步上下文</td>
<td>❌ 需手动控制</td>
<td>✅ 自动捕获</td>
</tr>
<tr>
<td>推荐度</td>
<td>⚠️ 特殊场景</td>
<td>✅ 日常使用</td>
</tr>
</tbody>
</table>
<p><strong>✅ 推荐</strong>:在现代代码中优先使用 <code>await</code>,<code>ContinueWith</code> 仅用于特殊场景(如需要指定 <code>TaskScheduler</code>)。</p>
<hr>
<h2 id="5️⃣-任务状态查询与判断">5️⃣ 任务状态:查询与判断</h2>
<h3 id="51-status-属性任务的生命周期">5.1 Status 属性:任务的生命周期</h3>
<pre><code class="language-csharp">Task task = new Task(() =>
{
Thread.Sleep(1000);
});
Console.WriteLine(task.Status); // Created
task.Start();
Console.WriteLine(task.Status); // Running 或 WaitingForActivation
task.Wait();
Console.WriteLine(task.Status); // RanToCompletion
</code></pre>
<p><strong>完整的状态枚举</strong>:</p>
<pre><code class="language-csharp">public enum TaskStatus
{
Created, // 已创建但未启动
WaitingForActivation, // 等待调度器激活
WaitingToRun, // 已排队等待执行
Running, // 正在执行
WaitingForChildrenToComplete, // 等待子任务完成
RanToCompletion, // 成功完成
Canceled, // 已取消
Faulted // 发生异常
}
</code></pre>
<hr>
<h3 id="52-常用状态属性">5.2 常用状态属性</h3>
<pre><code class="language-csharp">Task<int> task = Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
});
// 是否已完成(无论成功、失败还是取消)
bool isCompleted = task.IsCompleted;
// 是否成功完成
bool isSuccess = task.IsCompletedSuccessfully; // .NET Core 2.0+
// 是否发生异常
bool isFaulted = task.IsFaulted;
// 是否被取消
bool isCanceled = task.IsCanceled;
// 获取结果(阻塞等待)
if (task.IsCompletedSuccessfully)
{
int result = task.Result;
}
// 获取异常
if (task.IsFaulted)
{
AggregateException? exception = task.Exception;
}
</code></pre>
<hr>
<h3 id="53-实战轮询任务状态不推荐">5.3 实战:轮询任务状态(不推荐)</h3>
<pre><code class="language-csharp">// ❌ 错误:浪费 CPU 的轮询
Task<int> task = Task.Run(() =>
{
Thread.Sleep(2000);
return 42;
});
while (!task.IsCompleted)
{
Console.WriteLine("等待中...");
Thread.Sleep(100); // 浪费 CPU
}
Console.WriteLine($"结果: {task.Result}");
</code></pre>
<p><strong>✅ 正确:使用 await 或 Wait</strong></p>
<pre><code class="language-csharp">Task<int> task = Task.Run(() =>
{
Thread.Sleep(2000);
return 42;
});
int result = await task; // 或 task.Wait();
Console.WriteLine($"结果: {result}");
</code></pre>
<hr>
<h2 id="6️⃣-高级主题taskscheduler-与-taskfactory">6️⃣ 高级主题:TaskScheduler 与 TaskFactory</h2>
<h3 id="61-taskscheduler控制任务的执行位置">6.1 TaskScheduler:控制任务的执行位置</h3>
<pre><code class="language-csharp">// 默认调度器:使用线程池
Task.Run(() => Console.WriteLine("线程池执行"));
// 获取当前调度器
TaskScheduler current = TaskScheduler.Current;
TaskScheduler defaultScheduler = TaskScheduler.Default;
// 在 UI 线程上执行(WPF/WinForms)
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
// 这里的代码在 UI 线程执行
// button1.Text = "更新";
}, CancellationToken.None,
TaskCreationOptions.None,
uiScheduler);
</code></pre>
<hr>
<h3 id="62-taskfactory批量创建任务">6.2 TaskFactory:批量创建任务</h3>
<p><code>TaskFactory</code> 允许你创建一个带有默认配置的任务工厂,用于批量创建具有相同设置的任务。</p>
<h4 id="-taskfactory-构造函数详解">📌 <strong>TaskFactory 构造函数详解</strong></h4>
<pre><code class="language-csharp">TaskFactory factory = new TaskFactory(
CancellationToken.None, // 参数 1: 取消令牌
TaskCreationOptions.LongRunning, // 参数 2: 任务创建选项
TaskContinuationOptions.None, // 参数 3: 延续选项
TaskScheduler.Default // 参数 4: 任务调度器
);
</code></pre>
<p><strong>构造函数参数说明</strong>:</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>类型</th>
<th>说明</th>
<th>常用值</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>cancellationToken</strong></td>
<td><code>CancellationToken</code></td>
<td>用于取消工厂创建的所有任务</td>
<td><code>CancellationToken.None</code> (不取消)<br><code>cts.Token</code> (支持取消)</td>
</tr>
<tr>
<td><strong>creationOptions</strong></td>
<td><code>TaskCreationOptions</code></td>
<td>任务创建时的行为选项</td>
<td><code>None</code>、<code>LongRunning</code>、<code>AttachedToParent</code> 等</td>
</tr>
<tr>
<td><strong>continuationOptions</strong></td>
<td><code>TaskContinuationOptions</code></td>
<td>延续任务的默认选项</td>
<td><code>None</code>、<code>OnlyOnRanToCompletion</code> 等</td>
</tr>
<tr>
<td><strong>scheduler</strong></td>
<td><code>TaskScheduler</code></td>
<td>任务调度器,决定任务在哪里执行</td>
<td><code>TaskScheduler.Default</code> (ThreadPool)<br><code>TaskScheduler.FromCurrentSynchronizationContext()</code> (UI 线程)</td>
</tr>
</tbody>
</table>
<hr>
<h4 id="-taskcreationoptions-枚举详解">🔧 <strong>TaskCreationOptions 枚举详解</strong></h4>
<p>这是最重要的配置选项,直接影响任务的执行方式和性能。</p>
<pre><code class="language-csharp">
public enum TaskCreationOptions
{
None = 0x0, // 默认:无特殊选项
PreferFairness = 0x1, // 优先公平性(FIFO 调度)
LongRunning = 0x2, // 长时间运行(使用独立线程)
AttachedToParent = 0x4, // 附加到父任务
DenyChildAttach = 0x8, // 拒绝子任务附加
HideScheduler = 0x10, // 隐藏调度器(防止子任务继承)
RunContinuationsAsynchronously = 0x40// 异步运行延续
}
</code></pre>
<h5 id="1️⃣-none默认值"><strong>1️⃣ None(默认值)</strong></h5>
<pre><code class="language-csharp">Task.Run(() => DoWork());// 等价于 TaskCreationOptions.None
</code></pre>
<ul>
<li><strong>行为</strong>:使用 ThreadPool 线程,默认调度策略(工作窃取)</li>
<li><strong>适用场景</strong>:99% 的普通任务</li>
<li><strong>性能特点</strong>:高效,线程复用</li>
</ul>
<hr>
<h5 id="2️⃣-longrunning长时间运行"><strong>2️⃣ LongRunning(长时间运行)</strong></h5>
<pre><code class="language-csharp">Task.Factory.StartNew(() =>
{
while (true)
{
// 长时间运行的循环
Thread.Sleep(1000);
}
}, TaskCreationOptions.LongRunning);
</code></pre>
<p><strong>详细说明</strong>:</p>
<ul>
<li><strong>行为</strong>:<strong>创建独立的专用线程</strong>,而不是使用 ThreadPool 线程</li>
<li><strong>线程类型</strong>:<code>new Thread()</code> 创建的后台线程</li>
<li><strong>何时使用</strong>:
<ul>
<li>✅ 任务运行时间超过 <strong>1 秒以上</strong></li>
<li>✅ 阻塞式操作(如 <code>Thread.Sleep</code>、<code>BlockingCollection.Take()</code>)</li>
<li>✅ 无限循环的后台服务(如消息队列监听)</li>
</ul>
</li>
<li><strong>性能影响</strong>:
<ul>
<li>✅ 不占用 ThreadPool 线程(避免线程池饥饿)</li>
<li>⚠️ 创建线程有开销(~1ms + 1MB 栈内存)</li>
<li>⚠️ 不适合大量短任务(会创建大量线程)</li>
</ul>
</li>
</ul>
<p><strong>⚠️ 常见误用</strong>:</p>
<pre><code class="language-csharp">// ❌ 错误:短任务使用 LongRunning(性能浪费)
Task.Factory.StartNew(() =>
{
Console.WriteLine("快速任务");
}, TaskCreationOptions.LongRunning);// 创建线程的开销比任务本身还大!
// ✅ 正确:使用默认选项
Task.Run(() => Console.WriteLine("快速任务"));
</code></pre>
<hr>
<h5 id="3️⃣-attachedtoparent附加到父任务"><strong>3️⃣ AttachedToParent(附加到父任务)</strong></h5>
<pre><code class="language-csharp">Task parent = Task.Factory.StartNew(() =>
{
Console.WriteLine("父任务开始");
// 子任务附加到父任务
Task child = Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
Console.WriteLine("子任务完成");
}, TaskCreationOptions.AttachedToParent);
Console.WriteLine("父任务逻辑完成");
});
parent.Wait();// ⚠️ 会等待子任务也完成!
Console.WriteLine("父任务真正完成");
</code></pre>
<p><strong>行为</strong>:</p>
<ul>
<li>父任务的 <code>IsCompleted</code> 会等待所有附加的子任务完成</li>
<li>子任务的异常会传播到父任务</li>
<li>父任务的取消会传播到子任务</li>
</ul>
<p><strong>执行输出</strong>:</p>
<pre><code>父任务开始
父任务逻辑完成
(等待 2 秒)
子任务完成
父任务真正完成
</code></pre>
<p><strong>⚠️ 现代代码中很少使用</strong>:</p>
<ul>
<li><code>async/await</code> 提供了更清晰的层次结构</li>
<li>异常传播容易混乱</li>
<li>调试困难</li>
</ul>
<hr>
<h5 id="4️⃣-denychildattach拒绝子任务附加"><strong>4️⃣ DenyChildAttach(拒绝子任务附加)</strong></h5>
<pre><code class="language-csharp">Task parent = Task.Factory.StartNew(() =>
{
// 即使子任务使用 AttachedToParent,也不会附加
Task child = Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
Console.WriteLine("子任务完成");
}, TaskCreationOptions.AttachedToParent);// 无效!被拒绝
Console.WriteLine("父任务完成");
}, TaskCreationOptions.DenyChildAttach);
parent.Wait();// 立即返回,不等待子任务
</code></pre>
<p><strong>用途</strong>:</p>
<ul>
<li>防止第三方库的代码意外附加子任务</li>
<li>保证任务独立性</li>
</ul>
<hr>
<h5 id="5️⃣-preferfairness优先公平性"><strong>5️⃣ PreferFairness(优先公平性)</strong></h5>
<pre><code class="language-csharp">for (int i = 0; i < 10; i++)
{
int id = i;
Task.Factory.StartNew(() =>
{
Console.WriteLine($"任务 {id}");
}, TaskCreationOptions.PreferFairness);
}
</code></pre>
<p><strong>详细说明</strong>:</p>
<ul>
<li><strong>行为</strong>:使用 <strong>FIFO(先进先出)队列</strong> 调度任务,而不是默认的工作窃取队列</li>
<li><strong>默认调度</strong>(无此选项):
<ul>
<li>ThreadPool 使用<strong>工作窃取算法</strong></li>
<li>新任务优先放入本地队列(LIFO)</li>
<li>其他线程可以窃取(FIFO)</li>
<li><strong>优势</strong>:缓存友好,性能高</li>
</ul>
</li>
<li><strong>FIFO 调度</strong>(此选项):
<ul>
<li>所有任务放入全局队列</li>
<li>严格按提交顺序执行</li>
<li><strong>优势</strong>:公平,避免某些任务饥饿</li>
<li><strong>劣势</strong>:性能略低(全局队列竞争)</li>
</ul>
</li>
</ul>
<p><strong>何时使用</strong>:</p>
<ul>
<li>✅ 任务执行顺序很重要</li>
<li>✅ 需要避免某些任务长期得不到执行</li>
<li>⚠️ 性能不是首要考虑</li>
</ul>
<p><strong>性能对比</strong>:</p>
<pre><code>默认(工作窃取): 任务 0 → 任务 2 → 任务 1 → 任务 4 → 任务 3 ...(乱序,高性能)
PreferFairness: 任务 0 → 任务 1 → 任务 2 → 任务 3 → 任务 4 ...(顺序,公平)
</code></pre>
<hr>
<h5 id="6️⃣-hidescheduler隐藏调度器"><strong>6️⃣ HideScheduler(隐藏调度器)</strong></h5>
<pre><code class="language-csharp">TaskScheduler customScheduler = new CustomTaskScheduler();
Task parent = Task.Factory.StartNew(() =>
{
// 子任务不会继承 customScheduler
Task child = Task.Run(() =>
{
Console.WriteLine($"子任务调度器:{TaskScheduler.Current}");
// 输出:System.Threading.Tasks.TaskScheduler+ThreadPoolTaskScheduler
});
}, CancellationToken.None, TaskCreationOptions.HideScheduler, customScheduler);
</code></pre>
<p><strong>用途</strong>:</p>
<ul>
<li>防止子任务继承父任务的自定义调度器</li>
<li>确保子任务在默认 ThreadPool 上执行</li>
</ul>
<hr>
<h5 id="7️⃣-runcontinuationsasynchronously异步运行延续"><strong>7️⃣ RunContinuationsAsynchronously(异步运行延续)</strong></h5>
<pre><code class="language-csharp">var tcs = new TaskCompletionSource<int>(
TaskCreationOptions.RunContinuationsAsynchronously);
tcs.Task.ContinueWith(t =>
{
Console.WriteLine($"延续运行在线程:{Thread.CurrentThread.ManagedThreadId}");
});
// 设置结果
Console.WriteLine($"主线程:{Thread.CurrentThread.ManagedThreadId}");
tcs.SetResult(42);// 延续会在 ThreadPool 线程执行,而不是当前线程
</code></pre>
<p><strong>详细说明</strong>:</p>
<ul>
<li><strong>默认行为</strong>(无此选项):
<ul>
<li>延续(<code>ContinueWith</code>)会在 <strong>调用 SetResult/SetException 的线程</strong> 上同步执行</li>
<li>风险:可能阻塞调用者</li>
</ul>
</li>
<li><strong>此选项行为</strong>:
<ul>
<li>延续会排队到 ThreadPool,<strong>异步执行</strong></li>
<li>调用 <code>SetResult</code> 的线程不会被阻塞</li>
</ul>
</li>
</ul>
<p><strong>何时使用</strong>:</p>
<ul>
<li>✅ 使用 <code>TaskCompletionSource</code> 时</li>
<li>✅ 延续可能执行耗时操作</li>
<li>✅ 避免阻塞调用 <code>SetResult</code> 的线程</li>
</ul>
<hr>
<h4 id="-组合使用flags-枚举">🎯 <strong>组合使用(Flags 枚举)</strong></h4>
<p><code>TaskCreationOptions</code> 是 Flags 枚举,可以组合多个选项:</p>
<pre><code class="language-csharp">Task.Factory.StartNew(() =>
{
// 长时间运行 + 拒绝子任务附加
}, TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach);
</code></pre>
<hr>
<h4 id="-实际应用示例">📊 <strong>实际应用示例</strong></h4>
<p><strong>示例 1:长时间运行的后台服务</strong></p>
<pre><code class="language-csharp">var cts = new CancellationTokenSource();
Task.Factory.StartNew(() =>
{
while (!cts.Token.IsCancellationRequested)
{
// 处理消息队列
ProcessMessages();
Thread.Sleep(100);
}
}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
// 稍后取消
cts.Cancel();
</code></pre>
<p><strong>示例 2:批量创建相同配置的任务</strong></p>
<pre><code class="language-csharp">// 创建工厂:所有任务都是长时间运行 + 支持取消
var cts = new CancellationTokenSource();
TaskFactory factory = new TaskFactory(
cts.Token,
TaskCreationOptions.LongRunning,
TaskContinuationOptions.None,
TaskScheduler.Default
);
// 批量创建
var tasks = new List<Task>();
for (int i = 0; i < 5; i++)
{
int id = i;
tasks.Add(factory.StartNew(() =>
{
while (!cts.Token.IsCancellationRequested)
{
Console.WriteLine($"工作线程 {id}");
Thread.Sleep(1000);
}
}));
}
// 稍后统一取消
cts.Cancel();
await Task.WhenAll(tasks);
</code></pre>
<hr>
<h4 id="️-现代代码建议">⚠️ <strong>现代代码建议</strong></h4>
<p><strong>在现代 C# 代码中</strong>:</p>
<ul>
<li>✅ <strong>99% 的情况使用 <code>Task.Run</code></strong>(默认 <code>TaskCreationOptions.None</code>)</li>
<li>✅ <strong>长时间运行的阻塞操作</strong>:使用 <code>TaskCreationOptions.LongRunning</code></li>
<li>✅ <strong>需要取消功能</strong>:传递 <code>CancellationToken</code>,而不是使用 TaskFactory</li>
<li>❌ <strong>避免使用 <code>AttachedToParent</code></strong>(用 <code>async/await</code> 代替)</li>
<li>❌ <strong>避免使用 <code>TaskFactory</code></strong>(除非批量创建相同配置的任务)</li>
</ul>
<p><strong>推荐写法</strong>:</p>
<pre><code class="language-csharp">// ✅ 现代写法(简洁清晰)
await Task.Run(() => DoWork(), cancellationToken);
// ❌ 旧式写法(冗长复杂)
TaskFactory factory = new TaskFactory(cancellationToken, ...);
await factory.StartNew(() => DoWork());
</code></pre>
<blockquote>
<p><strong>💡 关键要点</strong>:TaskFactory 主要用于遗留代码或需要精细控制任务创建的场景。日常开发中,<code>Task.Run</code> + <code>async/await</code> 是更好的选择。</p>
</blockquote>
<hr>
<h2 id="7️⃣-常见陷阱与最佳实践">7️⃣ 常见陷阱与最佳实践</h2>
<h3 id="陷阱-1滥用同步等待">陷阱 1:滥用同步等待</h3>
<pre><code class="language-csharp">// ❌ 极其危险:死锁风险(WPF/WinForms)
public void Button_Click(object sender, EventArgs e)
{
var result = GetDataAsync().Result; // 死锁!
}
async Task<string> GetDataAsync()
{
await Task.Delay(1000);
return "数据";
}
</code></pre>
<p><strong>死锁原因</strong>:</p>
<ol>
<li><code>GetDataAsync</code> 在 UI 线程启动</li>
<li><code>await Task.Delay</code> 注册延续回 UI 线程</li>
<li><code>.Result</code> 阻塞 UI 线程</li>
<li>延续无法执行 → 死锁</li>
</ol>
<p><strong>✅ 解决方案</strong>:</p>
<pre><code class="language-csharp">// 方案 1:使用 async 一路到底(强烈推荐)
public async void Button_Click(object sender, EventArgs e)
{
var result = await GetDataAsync();
}
// 方案 2:使用 ConfigureAwait(false)(第5章详解)
async Task<string> GetDataAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
return "数据";
}
</code></pre>
<hr>
<h3 id="陷阱-15同步方法中调用异步方法的最佳实践">陷阱 1.5:同步方法中调用异步方法的最佳实践</h3>
<p><strong>场景</strong>:有时你无法将整个调用链改为 <code>async</code>(如遗留代码、同步接口实现),必须在同步方法中调用异步方法。</p>
<pre><code class="language-csharp">// ❌ 最差:使用 .Result(死锁风险 + 包装异常)
public string GetData()
{
return GetDataAsync().Result;// AggregateException + 死锁
}
// ⚠️ 较差:使用 .Wait()(死锁风险)
public void ProcessData()
{
GetDataAsync().Wait();// 死锁风险
}
// ⚙️ 相对安全:ConfigureAwait(false).GetAwaiter().GetResult()
public string GetData()
{
return GetDataAsync()
.ConfigureAwait(false)// 不捕获上下文,降低死锁风险
.GetAwaiter()
.GetResult(); // 直接抛出原始异常,而非 AggregateException
}
// ✅ 最佳:改为异步方法
public async Task<string> GetDataAsync()
{
return await GetDataAsync();
}
</code></pre>
<p><strong>为什么 <code>.ConfigureAwait(false).GetAwaiter().GetResult()</code> 相对安全?</strong></p>
<ol>
<li>
<p><strong>ConfigureAwait(false)</strong>:</p>
<ul>
<li>不捕获 <code>SynchronizationContext</code></li>
<li>避免在 UI 线程等待时的死锁(但不是 100% 保证)</li>
</ul>
</li>
<li>
<p><strong>GetAwaiter().GetResult()</strong>:</p>
<ul>
<li>直接抛出原始异常(如 <code>InvalidOperationException</code>)</li>
<li>而不是包装在 <code>AggregateException</code> 中</li>
<li>异常堆栈更清晰</li>
</ul>
</li>
</ol>
<p><strong>⚠️ 但这仍然不是最佳实践</strong>:</p>
<ul>
<li>仍然<strong>阻塞线程</strong>(浪费 ThreadPool 资源)</li>
<li>在某些复杂场景下仍可能死锁</li>
<li><strong>最佳解决方案</strong>:将整个调用链改为 <code>async/await</code></li>
</ul>
<p><strong>💡 何时可以接受使用</strong>:</p>
<ol>
<li>
<p><strong>遗留代码迁移</strong>(无法改为 async):</p>
<pre><code class="language-csharp">// 旧的同步接口
public interface IDataRepository
{
string GetData();// 无法改为 async
}
// 实现时调用异步方法
public class DataRepository : IDataRepository
{
public string GetData()
{
return GetDataAsync()
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}
private async Task<string> GetDataAsync() { ... }
}
</code></pre>
</li>
<li>
<p><strong>Main 方法</strong>(.NET Framework):</p>
<pre><code class="language-csharp">// .NET Framework(无 top-level statements)
static void Main(string[] args)
{
MainAsync(args)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}
static async Task MainAsync(string[] args)
{
await DoWorkAsync();
}
</code></pre>
</li>
<li>
<p><strong>同步 Dispose</strong>(但更推荐 <code>IAsyncDisposable</code>):</p>
<pre><code class="language-csharp">public class MyClass : IDisposable
{
public void Dispose()
{
DisposeAsync()
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}
public async ValueTask DisposeAsync()
{
await CleanupAsync();
}
}
</code></pre>
</li>
</ol>
<blockquote>
<p><strong>📚 详细原理</strong>:关于 <code>SynchronizationContext</code>、上下文捕获、死锁机制的深入讲解,请参考第 05 章《SynchronizationContext 深入解析》。</p>
</blockquote>
<hr>
<h3 id="陷阱-2忘记-await-或-wait">陷阱 2:忘记 await 或 Wait</h3>
<pre><code class="language-csharp">// ❌ 错误:任务启动但没有等待
public void DoWork()
{
Task.Run(() =>
{
throw new Exception("错误");
});
Console.WriteLine("继续执行");
// 异常被吞掉,没有人观察到!
}
</code></pre>
<p><strong>✅ 正确</strong>:</p>
<pre><code class="language-csharp">public async Task DoWorkAsync()
{
await Task.Run(() =>
{
throw new Exception("错误");
});
Console.WriteLine("继续执行");
}
</code></pre>
<hr>
<h3 id="陷阱-3在循环中创建任务导致闭包问题">陷阱 3:在循环中创建任务导致闭包问题</h3>
<pre><code class="language-csharp">// ❌ 错误:所有任务都打印 10
var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
tasks.Add(Task.Run(() => Console.WriteLine(i)));
}
Task.WaitAll(tasks.ToArray());
</code></pre>
<p><strong>✅ 正确</strong>:</p>
<pre><code class="language-csharp">var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
int localI = i; // 捕获局部变量
tasks.Add(Task.Run(() => Console.WriteLine(localI)));
}
Task.WaitAll(tasks.ToArray());
</code></pre>
<hr>
<h3 id="陷阱-4误用-taskrun-包装已经是异步的方法">陷阱 4:误用 Task.Run 包装已经是异步的方法</h3>
<pre><code class="language-csharp">// ❌ 错误:双重异步,浪费资源
public Task<string> GetDataAsync()
{
return Task.Run(async () =>
{
return await httpClient.GetStringAsync("https://api.example.com");
});
}
// ✅ 正确:直接返回
public Task<string> GetDataAsync()
{
return httpClient.GetStringAsync("https://api.example.com");
}
</code></pre>
<p><strong>原则</strong>:</p>
<ul>
<li><code>Task.Run</code> 用于 <strong>CPU 密集型</strong> 操作</li>
<li>I/O 操作(网络、文件)本身就是异步的,无需 <code>Task.Run</code></li>
</ul>
<hr>
<h2 id="8️⃣-实战演练综合示例">8️⃣ 实战演练:综合示例</h2>
<h3 id="示例-1并发下载多个文件">示例 1:并发下载多个文件</h3>
<pre><code class="language-csharp">async Task DownloadFilesAsync(string[] urls)
{
using HttpClient client = new HttpClient();
// 创建所有下载任务
Task<string>[] tasks = urls.Select(url =>
client.GetStringAsync(url)
).ToArray();
// 并发执行
string[] results = await Task.WhenAll(tasks);
for (int i = 0; i < results.Length; i++)
{
Console.WriteLine($"文件 {i + 1}: {results.Length} 字节");
}
}
</code></pre>
<hr>
<h3 id="示例-2带重试的任务执行">示例 2:带重试的任务执行</h3>
<pre><code class="language-csharp">async Task<T> ExecuteWithRetryAsync<T>(
Func<Task<T>> operation,
int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await operation();
}
catch (Exception ex) when (i < maxRetries - 1)
{
Console.WriteLine($"第 {i + 1} 次失败: {ex.Message}");
await Task.Delay(1000 * (i + 1)); // 递增延迟
}
}
throw new InvalidOperationException("达到最大重试次数");
}
// 使用
var result = await ExecuteWithRetryAsync(async () =>
{
return await httpClient.GetStringAsync("https://api.example.com");
});
</code></pre>
<hr>
<h3 id="示例-3批量处理的限流控制">示例 3:批量处理的限流控制</h3>
<pre><code class="language-csharp">async Task ProcessItemsWithThrottleAsync<T>(
IEnumerable<T> items,
Func<T, Task> processor,
int maxConcurrency = 5)
{
using SemaphoreSlim semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = items.Select(async item =>
{
await semaphore.WaitAsync();
try
{
await processor(item);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
// 使用:最多同时处理 5 个
await ProcessItemsWithThrottleAsync(
Enumerable.Range(1, 100),
async i =>
{
await Task.Delay(1000);
Console.WriteLine($"处理 {i}");
},
maxConcurrency: 5
);
</code></pre>
<hr>
<h2 id="-本章总结task-api-快速参考卡片">🎓 本章总结:Task API 快速参考卡片</h2>
<blockquote>
<p>💡 <strong>全面掌握 Task 类的核心 API</strong> - 建议将此速查表加入书签或打印出来作为日常开发参考</p>
</blockquote>
<hr>
<h3 id="-创建任务">🎨 创建任务</h3>
<table>
<thead>
<tr>
<th>API</th>
<th>说明</th>
<th>使用场景</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Task.Run(() => {})</code></td>
<td>立即启动任务</td>
<td>✅ 日常首选</td>
<td><code>Task.Run(() => DoWork())</code></td>
</tr>
<tr>
<td><code>Task.Run<T>(() => {})</code></td>
<td>带返回值的任务</td>
<td>✅ 需要结果时</td>
<td><code>Task.Run(() => 42)</code></td>
</tr>
<tr>
<td><code>Task.Factory.StartNew()</code></td>
<td>高级控制</td>
<td>⚠️ 特殊场景</td>
<td><code>Task.Factory.StartNew(() => {}, TaskCreationOptions.LongRunning)</code></td>
</tr>
<tr>
<td><code>new Task(() => {})</code> + <code>Start()</code></td>
<td>手动启动</td>
<td>⚠️ 延迟启动</td>
<td><code>var task = new Task(() => {}); task.Start();</code></td>
</tr>
<tr>
<td><code>Task.FromResult(value)</code></td>
<td>已完成的任务</td>
<td>✅ 缓存/优化</td>
<td><code>Task.FromResult(42)</code></td>
</tr>
<tr>
<td><code>Task.CompletedTask</code></td>
<td>已完成的空任务</td>
<td>✅ 接口实现</td>
<td><code>return Task.CompletedTask;</code></td>
</tr>
<tr>
<td><code>Task.FromCanceled(token)</code></td>
<td>已取消的任务</td>
<td>⚠️ 特殊场景</td>
<td><code>Task.FromCanceled(cancellationToken)</code></td>
</tr>
<tr>
<td><code>Task.FromException(ex)</code></td>
<td>已失败的任务</td>
<td>⚠️ 特殊场景</td>
<td><code>Task.FromException(new Exception())</code></td>
</tr>
</tbody>
</table>
<hr>
<h3 id="-等待任务">⏳ 等待任务</h3>
<table>
<thead>
<tr>
<th>API</th>
<th>说明</th>
<th>阻塞/异步</th>
<th>推荐度</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>await task</code></td>
<td>异步等待</td>
<td>异步</td>
<td>✅ 推荐</td>
<td><code>await GetDataAsync()</code></td>
</tr>
<tr>
<td><code>task.Wait()</code></td>
<td>阻塞等待</td>
<td>阻塞</td>
<td>⚠️ 控制台可用</td>
<td><code>task.Wait()</code></td>
</tr>
<tr>
<td><code>task.Wait(timeout)</code></td>
<td>带超时等待</td>
<td>阻塞</td>
<td>⚠️ 特殊场景</td>
<td><code>task.Wait(5000)</code></td>
</tr>
<tr>
<td><code>Task.WaitAll(tasks)</code></td>
<td>等待所有任务</td>
<td>阻塞</td>
<td>⚠️ 用 WhenAll</td>
<td><code>Task.WaitAll(task1, task2)</code></td>
</tr>
<tr>
<td><code>Task.WaitAny(tasks)</code></td>
<td>等待任意一个</td>
<td>阻塞</td>
<td>⚠️ 用 WhenAny</td>
<td><code>Task.WaitAny(task1, task2)</code></td>
</tr>
<tr>
<td><code>task.Result</code></td>
<td>获取结果</td>
<td>阻塞</td>
<td>❌ 避免</td>
<td><code>int result = task.Result;</code></td>
</tr>
</tbody>
</table>
<hr>
<h3 id="-组合任务">🔀 组合任务</h3>
<table>
<thead>
<tr>
<th>API</th>
<th>说明</th>
<th>返回类型</th>
<th>使用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Task.WhenAll(tasks)</code></td>
<td>等待所有完成</td>
<td><code>Task</code> 或 <code>Task<T[]></code></td>
<td>✅ 并发执行多个任务</td>
</tr>
<tr>
<td><code>Task.WhenAny(tasks)</code></td>
<td>等待任意一个</td>
<td><code>Task<Task></code></td>
<td>✅ 超时控制、竞速</td>
</tr>
<tr>
<td><code>Task.WhenEach(tasks)</code></td>
<td>逐个处理完成的任务 (.NET 9+)</td>
<td><code>IAsyncEnumerable<Task<T>></code></td>
<td>✅ 实时处理</td>
</tr>
</tbody>
</table>
<p><strong>示例对比</strong>:</p>
<pre><code class="language-csharp">// WhenAll - 等待所有任务
var results = await Task.WhenAll(task1, task2, task3);
// results =
// WhenAny - 响应最快的
var firstTask = await Task.WhenAny(task1, task2, task3);
var result = await firstTask;
// WhenEach - 按完成顺序处理 (.NET 9+)
await foreach (var task in Task.WhenEach(task1, task2, task3))
{
var result = await task;
// 处理每个完成的任务
}
</code></pre>
<hr>
<h3 id="-任务状态">📊 任务状态</h3>
<table>
<thead>
<tr>
<th>属性</th>
<th>类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>task.Status</code></td>
<td><code>TaskStatus</code> 枚举</td>
<td>任务当前状态</td>
</tr>
<tr>
<td><code>task.IsCompleted</code></td>
<td><code>bool</code></td>
<td>是否已完成(成功/失败/取消)</td>
</tr>
<tr>
<td><code>task.IsCompletedSuccessfully</code></td>
<td><code>bool</code></td>
<td>是否成功完成 (.NET Core 2.0+)</td>
</tr>
<tr>
<td><code>task.IsFaulted</code></td>
<td><code>bool</code></td>
<td>是否发生异常</td>
</tr>
<tr>
<td><code>task.IsCanceled</code></td>
<td><code>bool</code></td>
<td>是否被取消</td>
</tr>
<tr>
<td><code>task.Exception</code></td>
<td><code>AggregateException?</code></td>
<td>异常信息(如果有)</td>
</tr>
<tr>
<td><code>task.Result</code></td>
<td><code>T</code></td>
<td>任务结果(⚠️ 阻塞)</td>
</tr>
</tbody>
</table>
<p><strong>TaskStatus 枚举值</strong>:</p>
<pre><code class="language-csharp">Created // 已创建但未启动
WaitingForActivation // 等待调度器激活
WaitingToRun // 已排队等待执行
Running // 正在执行
WaitingForChildrenToComplete // 等待子任务完成
RanToCompletion // ✅ 成功完成
Canceled // ⚠️ 已取消
Faulted // ❌ 发生异常
</code></pre>
<hr>
<h3 id="-任务延续">🔗 任务延续</h3>
<table>
<thead>
<tr>
<th>API</th>
<th>代码风格</th>
<th>推荐度</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>await</code></td>
<td>线性</td>
<td>✅ 推荐</td>
<td>现代异步编程标准</td>
</tr>
<tr>
<td><code>ContinueWith</code></td>
<td>回调</td>
<td>⚠️ 特殊场景</td>
<td>复杂、异常处理麻烦</td>
</tr>
</tbody>
</table>
<pre><code class="language-csharp">// ❌ ContinueWith (不推荐)
task.ContinueWith(t =>
{
if (t.IsFaulted) { /* 处理异常 */ }
else { var result = t.Result; }
});
// ✅ await (推荐)
try
{
var result = await task;
}
catch (Exception ex)
{
// 处理异常
}
</code></pre>
<hr>
<h3 id="️-常见陷阱速查">⚠️ 常见陷阱速查</h3>
<table>
<thead>
<tr>
<th>陷阱</th>
<th>问题</th>
<th>解决方案</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>忘记 await/Wait</strong></td>
<td>异常被吞掉</td>
<td>✅ 总是 await 或 Wait 任务</td>
</tr>
<tr>
<td><strong>循环闭包</strong></td>
<td>所有任务捕获相同的变量</td>
<td>✅ 使用局部变量:<code>int local = i;</code></td>
</tr>
<tr>
<td><strong>双重异步</strong></td>
<td><code>Task.Run(async () => await ...)</code></td>
<td>✅ I/O 操作直接 await,不要用 Task.Run</td>
</tr>
<tr>
<td><strong>UI 死锁</strong></td>
<td><code>.Result</code> / <code>.Wait()</code> 在 UI 线程</td>
<td>✅ 使用 async/await 一路到底</td>
</tr>
<tr>
<td><strong>ASP.NET 阻塞</strong></td>
<td><code>.Result</code> 浪费线程</td>
<td>✅ 控制器方法使用 async Task<iactionresult></iactionresult></td>
</tr>
</tbody>
</table>
<hr>
<h3 id="-最佳实践速查">🎯 最佳实践速查</h3>
<p><strong>✅ 推荐做法</strong>:</p>
<ol>
<li><strong>创建任务</strong>:使用 <code>Task.Run</code>(CPU 密集型)</li>
<li><strong>等待任务</strong>:使用 <code>await</code>(异步方法中)</li>
<li><strong>并发执行</strong>:使用 <code>Task.WhenAll</code></li>
<li><strong>超时控制</strong>:使用 <code>Task.WhenAny</code></li>
<li><strong>异常处理</strong>:使用 try-catch 包裹 await</li>
</ol>
<p><strong>❌ 避免做法</strong>:</p>
<ol>
<li>在 UI/ASP.NET 中使用 <code>.Result</code> 或 <code>.Wait()</code></li>
<li>忘记 await 导致异常被忽略</li>
<li>用 <code>Task.Run</code> 包装已经是异步的方法</li>
<li>在循环中直接捕获循环变量</li>
<li>使用轮询检查 <code>IsCompleted</code></li>
</ol>
<hr>
<h3 id="-何时用什么">💡 何时用什么?</h3>
<pre><code>需要并发执行 CPU 密集型操作?
→ Task.Run
需要等待多个任务完成?
→ await Task.WhenAll(tasks)
需要超时控制?
→ await Task.WhenAny(task, Task.Delay(timeout))
需要重试逻辑?
→ 自己实现重试循环 + await
需要限制并发数?
→ SemaphoreSlim + Task.WhenAll
I/O 操作(网络、文件)?
→ 直接使用异步 API,不要用 Task.Run
</code></pre>
<hr>
<h3 id="-核心要点回顾">📋 核心要点回顾</h3>
<p><strong>创建任务</strong>:</p>
<ul>
<li>✅ 优先使用 <code>Task.Run</code></li>
<li>⚠️ <code>Task.Factory.StartNew</code> 仅用于高级场景</li>
<li>❌ 避免 <code>new Task()</code> 忘记 <code>Start()</code></li>
</ul>
<p><strong>等待任务</strong>:</p>
<ul>
<li>✅ 异步代码中使用 <code>await</code></li>
<li>❌ 避免在 UI/ASP.NET 中使用 <code>.Result</code> 或 <code>.Wait()</code></li>
<li>并发用 <code>WhenAll</code>,竞速用 <code>WhenAny</code></li>
</ul>
<p><strong>任务组合</strong>:</p>
<ul>
<li><code>Task.WhenAll</code>:等待所有任务完成</li>
<li><code>Task.WhenAny</code>:响应最快的任务</li>
<li><code>Task.WhenEach</code>:按完成顺序处理(.NET 9+)</li>
</ul>
<p><strong>状态查询</strong>:</p>
<ul>
<li><code>IsCompleted</code>、<code>IsFaulted</code>、<code>IsCanceled</code></li>
<li>避免轮询状态,使用 <code>await</code> 或 <code>Wait</code></li>
</ul>
<p><strong>最佳实践</strong>:</p>
<ul>
<li>异步一路到底(async all the way)</li>
<li>CPU 密集型用 <code>Task.Run</code>,I/O 操作直接用异步 API</li>
<li>注意闭包陷阱和死锁风险</li>
</ul>
<hr>
<h3 id="下一章预告">下一章预告</h3>
<p>在下一章《async/await 原理与性能优化》中,我们将深入探讨:</p>
<ul>
<li>async/await 的编译器魔法:状态机是如何生成的?</li>
<li>为什么 async/await 不等于多线程?</li>
<li>如何通过 ValueTask 优化性能?</li>
<li>同步上下文的捕获与恢复</li>
</ul>
<p><strong>推荐练习</strong>:</p>
<ol>
<li>实现一个支持超时和重试的 HTTP 请求方法</li>
<li>使用 <code>Task.WhenAll</code> 并发调用多个 API</li>
<li>对比 <code>Task.Run</code> 和直接使用异步 API 的性能差异</li>
</ol>
<hr>
<h2 id="-参考资源">📚 参考资源</h2>
<h3 id="官方文档与源码">官方文档与源码</h3>
<ol>
<li>
<p><strong>Task 类源码</strong>(.NET Runtime)</p>
<ul>
<li>System.Threading.Tasks.Task</li>
<li>Microsoft 官方开源的 Task 实现,可以深入了解底层细节</li>
<li>推荐关注:<code>InternalWait</code>、<code>ContinueWith</code>、状态机相关代码</li>
</ul>
</li>
<li>
<p><strong>官方文档 - 基于任务的异步模式(TAP)</strong></p>
<ul>
<li>Task-based Asynchronous Pattern (TAP)</li>
<li>微软官方的异步编程模式指南</li>
<li>包含最佳实践、性能优化建议</li>
</ul>
<hr>
</li>
</ol><br><br>
来源:https://www.cnblogs.com/diamondhusky/p/19891861
頁:
[1]