摘要:本文围绕C#中的异步编程展开,详细介绍了
async/await
原理、Task
并行库、异步流(IAsyncEnumerable
)以及取消令牌(CancellationToken
)等关键内容。通过丰富的代码示例和详细的实操流程,帮助读者深入理解C#异步编程的概念、原理和应用场景,提升在实际项目中运用异步编程解决问题的能力。
文章目录
【C#基础:第五部分 实用编程】异步编程
关键词
C#;异步编程;async/await;Task并行库;异步流;取消令牌
一、引言
在现代软件开发中,异步编程已经成为处理I/O密集型和CPU密集型操作的重要手段。传统的同步编程在处理耗时操作时会阻塞线程,导致程序响应变慢,用户体验不佳。而异步编程允许程序在执行耗时操作时不阻塞当前线程,从而可以继续执行其他任务,提高程序的性能和响应能力。C#提供了强大的异步编程支持,包括async/await
关键字、Task
并行库、异步流和取消令牌等特性,使得开发者能够更加方便地实现异步操作。本文将深入探讨这些异步编程特性,帮助读者掌握C#异步编程的核心知识。
二、async/await原理
2.1 基本概念
async/await
是C#中用于简化异步编程的语法糖。async
关键字用于修饰方法,表示该方法是一个异步方法。异步方法通常会返回一个Task
或Task<T>
对象。await
关键字只能在async
方法内部使用,用于等待一个Task
或Task<T>
对象完成,并获取其结果。
2.2 工作原理
当调用一个async
方法时,方法内部的代码会正常执行,直到遇到await
关键字。此时,await
会检查关联的Task
是否已经完成。如果Task
已经完成,await
会立即返回Task
的结果;如果Task
尚未完成,await
会暂停当前async
方法的执行,并将控制权返回给调用者。当Task
完成后,async
方法会从await
语句处继续执行。
2.3 代码示例
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("开始执行异步方法");
int result = await CalculateAsync();
Console.WriteLine($"异步方法执行结果: {
result}");
Console.WriteLine("主线程继续执行其他任务");
}
static async Task<int> CalculateAsync()
{
Console.WriteLine("异步方法开始执行");
await Task.Delay(2000); // 模拟耗时操作
Console.WriteLine("异步方法执行完成");
return 42;
}
}
2.4 代码解释
Main
方法被标记为async
,并且返回Task
。在Main
方法中,调用了CalculateAsync
方法并使用await
关键字等待其结果。CalculateAsync
方法也是一个异步方法,它返回一个Task<int>
对象。在方法内部,使用Task.Delay(2000)
模拟一个耗时2秒的操作。
2.5 实操流程
- 打开Visual Studio,创建一个新的C#控制台应用程序项目。
- 将上述代码复制到
Program.cs
文件中。 - 按下F5键运行程序,观察程序的输出结果,理解
async/await
的工作原理。
2.6 注意事项
async
方法不一定必须包含await
关键字,但如果不包含await
,该方法将以同步方式执行。await
关键字只能在async
方法内部使用。
三、Task并行库
3.1 基本概念
Task
并行库是.NET框架提供的一个用于并行编程和异步编程的强大工具。Task
类表示一个异步操作,可以通过Task.Run
方法在后台线程上执行一个操作,也可以使用Task.Factory.StartNew
方法创建并启动一个Task
。
3.2 创建和启动Task
3.2.1 使用Task.Run
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Console.WriteLine("主线程开始");
Task task = Task.Run(() =>
{
Console.WriteLine("后台任务开始执行");
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"后台任务: {
i}");
}
Console.WriteLine("后台任务执行完成");
});
Console.WriteLine("主线程继续执行其他任务");
task.Wait(); // 等待任务完成
Console.WriteLine("主线程结束");
}
}
3.2.2 使用Task.Factory.StartNew
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Console.WriteLine("主线程开始");
Task task = Task.Factory.StartNew(() =>
{
Console.WriteLine("后台任务开始执行");
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"后台任务: {
i}");
}
Console.WriteLine("后台任务执行完成");
});
Console.WriteLine("主线程继续执行其他任务");
task.Wait(); // 等待任务完成
Console.WriteLine("主线程结束");
}
}
3.3 任务的状态和结果
Task
类有多个属性用于表示任务的状态,如IsCompleted
、IsFaulted
、IsCanceled
等。对于返回结果的任务,可以使用Task<T>
类,通过Result
属性获取任务的结果。
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Console.WriteLine("主线程开始");
Task<int> task = Task.Run(() =>
{
Console.WriteLine("后台任务开始执行");
int sum = 0;
for (int i = 0; i < 10; i++)
{
sum += i;
}
Console.WriteLine("后台任务执行完成");
return sum;
});
Console.WriteLine("主线程继续执行其他任务");
int result = task.Result; // 获取任务结果
Console.WriteLine($"任务结果: {
result}");
Console.WriteLine("主线程结束");
}
}
3.4 任务的组合
可以使用Task.WhenAll
和Task.WhenAny
方法来组合多个任务。Task.WhenAll
方法等待所有任务完成,而Task.WhenAny
方法等待任何一个任务完成。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("主线程开始");
Task<int> task1 = Task.Run(() =>
{
Console.WriteLine("任务1开始执行");
Task.Delay(2000).Wait();
Console.WriteLine("任务1执行完成");
return 1;
});
Task<int> task2 = Task.Run(() =>
{
Console.WriteLine("任务2开始执行");
Task.Delay(1000).Wait();
Console.WriteLine("任务2执行完成");
return 2;
});
// 等待所有任务完成
int[] results = await Task.WhenAll(task1, task2);
Console.WriteLine($"任务1结果: {
results[0]}, 任务2结果: {
results[1]}");
// 等待任何一个任务完成
Task<int> completedTask = await Task.WhenAny(task1, task2);
Console.WriteLine($"第一个完成的任务结果: {
completedTask.Result}");
Console.WriteLine("主线程结束");
}
}
3.5 实操流程
- 打开Visual Studio,创建一个新的C#控制台应用程序项目。
- 将上述代码示例分别复制到
Program.cs
文件中,依次运行每个示例,观察程序的输出结果,理解Task
并行库的使用方法。
3.6 注意事项
- 使用
Task.Run
和Task.Factory.StartNew
时,要注意线程池的使用和资源管理。 - 在使用
Task.Result
属性时,如果任务尚未完成,会阻塞当前线程,建议使用await
关键字异步获取结果。
四、异步流(IAsyncEnumerable)
4.1 基本概念
异步流(IAsyncEnumerable<T>
)是.NET Core 3.0引入的一个新特性,用于处理异步生成的序列。与传统的IEnumerable<T>
不同,IAsyncEnumerable<T>
允许在生成序列元素时进行异步操作,例如从网络或文件中读取数据。
4.2 实现异步流
要实现一个异步流,需要定义一个返回IAsyncEnumerable<T>
的方法,并在方法内部使用yield return
关键字异步生成元素。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
await foreach (int number in GenerateNumbersAsync())
{
Console.WriteLine(number);
}
}
static async IAsyncEnumerable<int> GenerateNumbersAsync()
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(1000); // 模拟异步操作
yield return i;
}
}
}
4.3 异步流的消费
使用await foreach
语句来消费异步流中的元素。await foreach
会自动等待每个元素的生成,并在元素可用时进行处理。
4.4 实操流程
- 打开Visual Studio,创建一个新的C#控制台应用程序项目。
- 将上述代码复制到
Program.cs
文件中。 - 按下F5键运行程序,观察程序的输出结果,理解异步流的使用方法。
4.5 注意事项
- 异步流方法必须使用
async
关键字修饰,并且返回IAsyncEnumerable<T>
。 - 在使用
await foreach
时,要确保在async
方法内部使用。
五、取消令牌(CancellationToken)
5.1 基本概念
取消令牌(CancellationToken
)用于在异步操作中实现取消功能。当需要取消一个正在执行的异步操作时,可以通过取消令牌向操作发送取消请求,操作可以根据取消令牌的状态来决定是否停止执行。
5.2 使用取消令牌
5.2.1 创建取消令牌源
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task task = LongRunningTaskAsync(token);
// 模拟一段时间后取消任务
await Task.Delay(2000);
cts.Cancel();
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("任务已取消");
}
}
static async Task LongRunningTaskAsync(CancellationToken token)
{
try
{
for (int i =