404 Not Found

404 Not Found


nginx

异步编程

同步与异步

同步代码按顺序逐行执行,遇到耗时操作会阻塞当前线程,导致程序无法响应。异步代码在等待耗时操作时释放线程,让程序继续执行其他工作。

同步阻塞的问题:

异步的优势:

示例

CSHARP
using System;
using System.Threading;

Console.WriteLine("同步开始: " + DateTime.Now.ToString("HH:mm:ss"));
Thread.Sleep(2000);
Console.WriteLine("同步结束: " + DateTime.Now.ToString("HH:mm:ss"));
▶ 试一试
TEXT
同步开始: 10:00:00
同步结束: 10:00:02

Task 与 Task<T>

Task 表示一个异步操作,不返回结果。Task<T> 表示一个返回 T 类型结果的异步操作。它们是 C# 异步编程的核心类型。

类型 说明 示例
Task 无返回值的异步操作 Task.Delay(1000)
Task<T> 有返回值的异步操作 Task.FromResult(42)

示例

CSHARP
using System;
using System.Threading.Tasks;

Task noResultTask = Task.Delay(500);
Task<int> resultTask = Task.FromResult(42);

await noResultTask;
int value = await resultTask;
Console.WriteLine("获取的值: " + value);
▶ 试一试
TEXT
获取的值: 42

async/await 语法

async 修饰符标记方法为异步方法,await 运算符等待异步操作完成而不阻塞线程。await 只能在 async 方法内使用。

语法规则:

示例

CSHARP
using System;
using System.Threading.Tasks;

async Task DoWorkAsync()
{
    Console.WriteLine("工作开始: " + DateTime.Now.ToString("HH:mm:ss.fff"));
    await Task.Delay(1000);
    Console.WriteLine("工作结束: " + DateTime.Now.ToString("HH:mm:ss.fff"));
}

async Task<string> GetMessageAsync()
{
    await Task.Delay(500);
    return "异步消息已到达";
}

await DoWorkAsync();
string msg = await GetMessageAsync();
Console.WriteLine(msg);
▶ 试一试
TEXT
工作开始: 10:00:00.000
工作结束: 10:00:01.000
异步消息已到达

Task.Run

Task.Run 将 CPU 密集型工作卸载到线程池执行,避免阻塞当前线程。适用于计算密集型操作,不适用于 I/O 操作。

示例

CSHARP
using System;
using System.Threading.Tasks;

int ComputeHeavy()
{
    int result = 0;
    for (int i = 0; i < 100_000_000; i++)
    {
        result += i;
    }
    return result;
}

Console.WriteLine("开始计算...");
int total = await Task.Run(() => ComputeHeavy());
Console.WriteLine("计算结果: " + total);
▶ 试一试
TEXT
开始计算...
计算结果: 4999999950000000

Task.WhenAll / WhenAny / Delay

方法 说明
Task.WhenAll 等待所有 Task 完成
Task.WhenAny 等待任意一个 Task 完成
Task.Delay 异步等待指定毫秒数,替代 Thread.Sleep

示例

CSHARP
using System;
using System.Threading.Tasks;

async Task<string> FetchAsync(string site)
{
    await Task.Delay(new Random().Next(500, 1500));
    return site + " 加载完成";
}

Task<string> t1 = FetchAsync("站点A");
Task<string> t2 = FetchAsync("站点B");
Task<string> t3 = FetchAsync("站点C");

string[] all = await Task.WhenAll(t1, t2, t3);
Console.WriteLine("--- WhenAll 完成 ---");
foreach (string s in all)
{
    Console.WriteLine(s);
}

Task<string> first = await Task.WhenAny(t1, t2, t3);
Console.WriteLine("最先完成: " + first.Result);
▶ 试一试
TEXT
--- WhenAll 完成 ---
站点A 加载完成
站点B 加载完成
站点C 加载完成
最先完成: 站点A 加载完成

异步方法命名规范

异步方法名应以 Async 为后缀,这是 .NET 的约定,方便区分同步与异步方法。

规则:

示例

CSHARP
using System;
using System.Threading.Tasks;

async Task<string> ReadFileAsync(string path)
{
    await Task.Delay(200);
    return "读取了: " + path;
}

async Task SaveDataAsync(string data)
{
    await Task.Delay(100);
    Console.WriteLine("已保存: " + data);
}

string content = await ReadFileAsync("/data.txt");
await SaveDataAsync(content);
▶ 试一试
TEXT
已保存: 读取了: /data.txt

CancellationToken 取消机制

CancellationToken 用于协作式取消异步操作。调用方通过 CancellationTokenSource 发出取消信号,被调用方检查令牌并提前退出。

使用步骤:

  1. 创建 CancellationTokenSource
  2. cts.Token 传入异步方法
  3. 异步方法中定期检查 token.IsCancellationRequested 或传给可取消的 API
  4. 需要取消时调用 cts.Cancel()

示例

CSHARP
using System;
using System.Threading;
using System.Threading.Tasks;

async Task LongWorkAsync(CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        token.ThrowIfCancellationRequested();
        Console.WriteLine("步骤 " + (i + 1));
        await Task.Delay(300, token);
    }
    Console.WriteLine("全部完成");
}

using var cts = new CancellationTokenSource();
cts.CancelAfter(1200);

try
{
    await LongWorkAsync(cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("操作已取消");
}
▶ 试一试
TEXT
步骤 1
步骤 2
步骤 3
步骤 4
操作已取消

IAsyncEnumerable<T> 异步流

C# 8 引入 IAsyncEnumerable<T>,支持异步生成和消费数据流。使用 await foreach 逐项异步遍历,适用于分批读取数据库、流式网络响应等场景。

示例

CSHARP
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

async IAsyncEnumerable<int> GenerateNumbersAsync()
{
    for (int i = 1; i <= 5; i++)
    {
        await Task.Delay(200);
        yield return i;
    }
}

await foreach (int num in GenerateNumbersAsync())
{
    Console.WriteLine("收到: " + num);
}
▶ 试一试
TEXT
收到: 1
收到: 2
收到: 3
收到: 4
收到: 5

常见异步陷阱

⚠️ 避免 async void

async void 无法 await、异常无法捕获、会导致应用崩溃。仅在事件处理器中使用。

⚠️ 避免使用 .Result 和 .Wait()

在 UI 或 ASP.NET 上下文中调用 .Result.Wait() 可能导致死锁:异步方法等待调用者释放线程,调用者等待异步方法完成,双方互相等待。

⚠️ ConfigureAwait(false)

库代码中应使用 ConfigureAwait(false) 避免捕获同步上下文,防止死锁并提升性能。

示例

CSHARP
using System;
using System.Threading.Tasks;

async Task<string> BadMethodAsync()
{
    await Task.Delay(100);
    return "完成";
}

async Task SafeUsageAsync()
{
    string result = await BadMethodAsync();
    Console.WriteLine("正确用法: " + result);
}

async Task LibraryMethodAsync()
{
    string result = await BadMethodAsync().ConfigureAwait(false);
    Console.WriteLine("库代码用法: " + result);
}

await SafeUsageAsync();
await LibraryMethodAsync();
▶ 试一试
TEXT
正确用法: 完成
库代码用法: 完成

❓ 常见问题

Q async void 和 async Task 有什么区别?
A async void 无法 await、异常无法捕获,仅用于事件处理器;async Task 是标准做法,可 await 且异常可处理。
Q 什么时候用 Task.Run?
A 仅用于 CPU 密集型计算工作。I/O 操作应使用原生异步 API(如 ReadAsync),不要用 Task.Run 包装。
Q .Result 会导致什么问题?
A 在 UI/ASP.NET 同步上下文中会死锁,调用线程等待 Task 完成,而 Task 等待同步上下文释放,双方互相阻塞。
Q ConfigureAwait(false) 什么时候用?
A 在类库代码中使用,避免捕获同步上下文,防止死锁并提升性能。UI/ASP.NET 应用层代码通常不需要。

📖 小节

📝 作业

  1. 编写一个异步方法 DownloadAllAsync,使用 Task.WhenAll 同时下载 3 个模拟数据源,并返回所有结果的数组
  2. 编写一个支持 CancellationToken 的异步倒计时方法,每秒输出剩余秒数,取消时输出"倒计时已取消"
  3. 使用 IAsyncEnumerable<int> 生成斐波那契数列前 20 项,用 await foreach 遍历并输出
  4. 找出以下代码的问题并修正:async void Button_Click() { var data = GetDataAsync().Result; Console.WriteLine(data); }
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏