.NET 异步编程(异步方法、异步委托、CancellationToken、WhenAll、yield)
文章目录
- 异步方法
- 异步委托
- async方法缺点
- CancellationToken
- WhenAll
- yield
异步方法
“异步方法”:用async
关键字修饰的方法
- 异步方法的返回值一般是
Task<T>
,T是真正的返回值类型,Task<int>
。惯例:异步方法名字以 Async 结尾。 - 即使方法没有返回值,也最好把返回值声明为非泛型的Task。
- 调用泛型方法时,一般在方法前加上
await
,这样拿到的返回值就是泛型指定的T类型; - 异步方法的“传染性”:一个方法中如果有await调用,则这个方法也 必须修饰为async
static async Task Main(string[] args)
{
string fileName = "d:/1.txt";
File.Delete(fileName);
File.WriteAllTextAsync(fileName, "hello async");
string s = await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
}
static async Task<int> DownloadAsync(string url, string destFilePath)
{
string body;
using (HttpClient httpClient = new HttpClient())
{
body = await httpClient.GetStringAsync(url);
}
await File.WriteAllTextAsync(destFilePath, body);
return body.Length;
}
如果同样的功能,既有同步方法,又有异步方法,那么 首先使用异步方法。.NET5中,很多框架中的方法也都支持异步:Main、WinForm事件处理函数。
对于不支持的异步方法怎么办?Wait()
(无返回值);Result
(有返回值)。风险:死锁,尽量不用。
Tips:async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者来讲没区别的,因此对于接口中的方法或者抽象方法不能修饰为async。
异步委托
ThreadPool.QueueUserWorkItem(async(obj) => {
await SomeAsync();
});
用ILSpy反编译dll(.exe只是windows下的启动器)成C# 4.0版本,就能看到容易理解的底层IL代码。
await
、async
是“语法糖”,最终编译成“状态机调用”。
总结:async的方法会被C#编译器编译成一个类,会主要根据 await 调用进行切分为多个状态,对async方法的调用会被拆分为对MoveNext的调用。
用await看似是“等待”,经过编译后,其实没有“wait”。
async方法缺点
1、异步方法会生成一个类,运行效率没有普通方法高;
2、可能会占用非常多的线程;
static Task<string> ReadFileAsync(int num)
{
switch (num)
{
case 1:
return File.ReadAllTextAsync("d:/1.txt");
case 2:
return File.ReadAllTextAsync("d:/2.txt");
default:
throw new ArgumentException("num invalid");
}
}
只返回Task,不“拆完了再装”反编译上面的代码:只是普通的方法调用。
优点:运行效率更高,不会造成线程浪费。
返回值为Task的不一定都要标注async,标注async只是让我们可以更方便的await而已。
如果一个异步方法只是对别的异步方法调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B;把A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字。
Tips:如果想在异步方法中暂停一段时间,不要用Thread.Sleep()
,因为它会阻塞调用线程,而要用await Task.Delay()
CancellationToken
很多异步方法都有CancellationToken
参数,用于获得提前终止执行的信号,比如:请求超时、用户取消请求。
CancellationToken结构体
- bool IsCancellationRequested: 是否取消
- Register(Action callback): 注册取消监听
- ThrowIfCancellationRequested(): 如果任务被取消,执行到这句话就抛异常。
CancellationTokenSource
- CancelAfter():超时后发出取消信号
- Cancel(): 发出取消信号
- CancellationToken Token
ASP.NET Core开发中,一般不需要自己处理CancellationToken、CancellationTokenSource这些,只要做到“能转发CancellationToken就转发”即可。ASP.NET Core会对于用户请求中断进行处理。
WhenAll
Task类的重要方法:
Task<Task> WhenAny(IEnumerable<Task> tasks)
等,任何一个 Task完成,Task就完成Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks)
等,所有Task完成,Task才完成。用于等待多个任务执行结束,但是不在乎它们的执行顺序。FromResult()
创建普通数值的Task对象。
Task<string> t1 = File.ReadAllTextAsync("d:/1.txt");
Task<string> t2 = File.ReadAllTextAsync("d:/2.txt");
Task<string> t3 = File.ReadAllTextAsync("d:/3.txt");
string[] results = await Task.WhenAll(t1, t2, t3);
string s1 = results[0];
string s2 = results[1];
string s3 = results[2];
yield
yield return
不仅能够简化数据的返回,而且可以让数据处理“流水线化”,提升性能。
static IEnumerable<string> Test()
{
yield return "hello";
yield return "yzk";
yield return "youzack";
}
在旧版C#中,async方法中 不能用 yield
。从C# 8.0 开始,把返回值声明为IAsyncEnumerable
(不要带Task),然后遍历的时候用await foreach()
即可。
static async Task Main(string[] args)
{
await foreach(var s in Test())
{
Console.WriteLine(s);
}
}
static async IAsyncEnumerable<string> Test()
{
yield return "hello";
yield return "world";
yield return "youzack";
}