在上一篇文章《C#基于任务的异步编程基础(一)之Task管理多线程》中介绍了Task关键字的基本用法,这篇文章将主要介绍async关键字。
一、async的背后
当方法使用async标识之后,和普通方法有了什么区别,在async背后,编译器做了什么?
1.当使用async标识之后,编译器会理解为这个方法里面可能会用到await关键字,编译器将会在状态机中编译此方法,当方法执行到await关键字时会处于挂起的状态直到该await标记的异步动作完成后才恢复继续执行方法后面的动作。
2.编译器会将运行解析方法的结果存储到返回类型中,通常是Task或者Task<TResult>,如果返回值为void,会将可能出现的异常存储到上下文中。
3.async标识之后,并不会影响或者改变方法同步或者异步执行,在当前线程中一直同步执行方法体内的代码,直到遇见await关键字,也就是说真正异步执行的是await关键字而非async关键字,async仅仅是表示该方法内会有异步执行的方法。
4.当是使用Task存储调用async标识的方法结果时会立即返回,不会阻塞线程,直至遇到.Reslut才会阻塞线程,因为需要等待执行结果
二、不使用await关键字
不使用await关键字,仅仅使用async标识方法,编译器会给出提醒,但方法会正常以同步的方式运行。如下代码:
static void Main(string[] args)
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "返回值:" + WriteNameAsync("小李").Result);
Console.ReadKey();
}
private static async Task<string> WriteNameAsync(string name)
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
Console.WriteLine(DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
return name;
}
执行结果如下:
可以看到当前线程和方法内的线程是同一个。
三、await关键字的使用
上文说了,使用async标记后的方法,当方法内有await关键字时,执行到await关键字时会处于挂起的状态直到该await标记的异步动作完成后才恢复继续执行方法后面的动作。下面就使用代码验证,如下代码:
static void Main(string[] args)
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "返回值:" + WriteNameAsync("小李").Result);
Console.ReadKey();
}
private static async Task<string> WriteNameAsync(string name)
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
Console.WriteLine("执行WriteNameAsync方法:" + DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
var task = await Task.Run(() => WriteName("小明"));
Console.WriteLine("执行WriteNameAsync方法:" + DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
return task;
}
private static string WriteName(string name)
{
Thread.Sleep(3000);//当前线程等待三秒
string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
Console.WriteLine("执行WriteName方法:" + DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
return name;
}
执行结果如图:
从上面得出结论:遇见await关键字事,主线程会等待挂起,直到await标识异步的方法执行完毕才会继续执行方法后的动作
四、await意义何在
从上面代码示例中可以看出,使用async关键字标识方法内可能使用await关键字,假如使用了await关键字,那么即使使用Task创建了一个新线程异步执行,但async标识的方法也得异步方法执行结束后才继续执行,那么这样和同步执行又有何区别?这样使用await关键字的意义又在哪里?
使用await关键字的意义在于,当判断某个方法可能需要耗时较长时,我们可以把它丢给Task,创建一个新的线程去执行,而我们的主线程可以继续做其他的事,这点理解起来可能和上文所述等待await异步方法执行结束才执行后续代码相违背。其实不然,这点请注意上文所述:当是使用Task存储调用async标识的方法结果时会立即返回。
将主线程方法修改成以下代码时,即可清楚理解:
static void Main(string[] args)
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "主线程即将执行异步方法");
Task<string> task = WriteNameAsync("小李");
for (int i = 0; i < 5; i++)
{
Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "主线程执行异步方法后的动作:" + i);
}
Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "主线程执行异步方法结果:"+task.Result);
Console.ReadKey();
}
执行结果如下:
结论:
只有灵活使用Task、async、await才能更好的写出优雅简洁的异步编程的代码,单独使用其中一个并不会发挥其应有的作用,熟练的配合使用就需要丰富的经验作为基础。