用async关键字修饰方法后,就变成了异步方法。需要注意几点:
await关键字的意思是:调用异步方法,等异步方法执行结束后再继续向下执行。如果不加await,则不等待异步方法执行结束,就向下执行。
async Task<int> DownloadAsync(string url, string destFilePath)
{
using HttpClient httpClient = new HttpClient();
string body = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(destFilePath, body);
return body.Length;
}
Console.WriteLine("开始下载人邮社网站");
int i1 = await DownloadAsync("https://www.ptpress.com.cn", "d:/ptpress.html");
Console.WriteLine($"下载完成,长度{i1}");
async异步方法的本质是:在对异步方法进行await调用时的等待时间(比如等待下载),会把当前的线程返回到线程池,等异步方法调用结束后,再从线程池中取出一个线程执行后面的代码。
使用async异步方法要用多任务的概念,如果是一个任务,则使用常规的方法更好。例如餐厅客人叫服务员点菜,当来了多个客人时,某个客人叫服务员来点菜,服务员将菜单给到这个客人,然后不需要一直等着,等到这个客人点菜成功,再叫服务员,这个时候服务员拿着点完的菜单交给厨师。这样做的好处是,这个服务员可以在某个客人点菜的时候,去服务其他顾客。
异步方法使用的技术是线程池,不等通于多线程。
对于async方法,编译器会把代码根据await调用分为若干片段,对于不同的片段使用状态机的方式切换执行。
//这个虽然是async方法,内部并没有await,它的内部并没有将任务放到其他线程中去执行
//这样写和普通方法一样
static async Task<decimal> CalcAsync(int n)
{
Console.WriteLine("CalcAsync:" + Thread.CurrentThread.ManagedThreadId);
decimal result = 1;
Random rand = new Random();
for (int i = 0; i < n * n; i++)
{
result = result + (decimal)rand.NextDouble();
}
return result;
}
//使用Task.Run方法,把要执行的代码以委托的形式传递给Task.Run,这样系统会从线程池中取出一个线程去执行代码
//这样才实现了异步
async Task<decimal> CalcAsync(int n)
{
Console.WriteLine("CalcAsync:" + Thread.CurrentThread.ManagedThreadId);
return await Task.Run(() => {
Console.WriteLine("Task.Run:" + Thread.CurrentThread.ManagedThreadId);
decimal result = 1;
Random rand = new Random();
for (int i = 0; i < n * n; i++)
{
result = result + (decimal)rand.NextDouble();
}
return result;
});
}
//由此可见:
//异步方法的定义中,也需要使用线程池的操作
//在调用异步方法使用await时,await有2个作用:
//1、等待异步方法执行结束再往下执行,因为异步方法的定义中使用了线程池操作(Task.Run),按照正常的执行程序,他会在新线程中执行,而不会等待其执行完成,所以在线程池操作前面也加了await以示和普通线程池操作的区别。
//2、会自动将Task类型的返回值识别为T类型
只要返回值是Task类型,就可以使用await关键字对其进行调用,而不用管是否用async修饰
如果一个异步方法只是对别的异步方法进行简单调用,无太多复杂逻辑(如获取异步方法的返回值并做处理),则可以直接去掉async修饰符
对于使用async的异步方法,编译器会负责将返回值转换为Task对象(reurn 3 自动转换为Task
),但是不带async修饰符的异步方法,需要自己自定义Task对象。
string s1 = await ReadFileAsync(1);
Console.WriteLine(s1);
Task<string> ReadFileAsync(int num)
{
switch (num)
{
case 1:
return File.ReadAllTextAsync("d:/1.txt"); //返回Task对象
case 2:
return File.ReadAllTextAsync("d:/2.txt");
default:
return Task.CompleterTask;//返回Task对象,需要自定义,不能直接return,因为无async修饰
//public static Task CompletedTask { get; }
}
}
重要问题:
Result
属性或者GetAwaiter().GetResult
方法来等待异步执行结束后获取返回值。对于Task返回值类型,可以使用Wait
方法来调用异步方法并等待任务结束,但不推荐这样,会造成阻塞调用线程。string s1 = File.ReadAllTextAsync("./1.txt").Result;
string s2 = File.ReadAllTextAsync("./1.txt").GetAwaiter().GetResult();
File.ReadAllTextAsync("./1.txt").Wait();
using HttpClient httpClient = new HttpClient();
string s1 = await httpClient.GetStringAsync("https://www.ptpress.com.cn");
await Task.Delay(3000);//不要使用Thread.sleep会阻塞调用线程
string s2 = await httpClient.GetStringAsync("https://www.rymooc.com");
CancellationToken
参数,则可以使用该对象提前终止操作。Task.WhenAll
同时等待多个Task的执行结束。另外也有Task.WhenAny
只要有一个任务完成,代码就向下执行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];