异步方法

“异步方法”:用async关键字修饰的方法

  1. 异步方法的返回值一般是Task<T>,T是真正的返回值类型,Task<int>。惯例:异步方法名字以 Async 结尾。
  2. 即使方法没有返回值,也最好把返回值声明为非泛型的Task。
  3. 调用泛型方法时,一般在方法前加上await,这样拿到的返回值就是泛型指定的T类型;
  4. 异步方法的“传染性”:一个方法中如果有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代码。
awaitasync是“语法糖”,最终编译成“状态机调用”。

.NET 异步编程(异步方法、异步委托、CancellationToken、WhenAll、yield)-LMLPHP

总结: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类的重要方法:

  1. Task<Task> WhenAny(IEnumerable<Task> tasks)等,任何一个 Task完成,Task就完成
  2. Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks)等,所有Task完成,Task才完成。用于等待多个任务执行结束,但是不在乎它们的执行顺序。
  3. 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";
}
03-20 06:26