先假设如下场景:
主函数 Main,循环等待用户输入;
计算函数 Cal,耗时计算大量数据;
class Test
{static int Main(string[] args){while(true){// 等待用户输入}}public static int Cal() {int sum = 0;for (int i = 0; i < 999; i++){sum = sum + i;}Console.WriteLine($"sum={sum}");return sum;}
}
为了在Main函数中调用Cal函数,同时Cal函数不阻塞主函数的循环,此时需要考虑增加一个CalAsync函数使Cal函数异步执行。
传统的思维方法 在CalAsync函数中启动一个线程,并在线程中执行Cal函数:
// using System.Threading;
public static CalAsync()
{Thread td = new Thread(new ThreadStart(Cal));td.start();
}
这种方法显示地创建了一个线程并启动执行,CalAsync函数本身还是在主线程执行并且无法直接获取Cal函数的结果。
async 和 await 异步编程方法 使用async标记CalAsync函数,并在CalAsync函数中创建任务Task异步执行Cal函数,同时使用await标记获取Cal函数的执行结果:
// using System.Threading.Tasks;
public static aysnc void CalAsync()
{int result = await Task.Run(new Func<int>(Cal));// 或使用lambda书写方式// int result = await Task.Run(() => test());Console.WriteLine(result);
}
在Main函数中直接调用CalAsync函数,可以发现CalAsync成功调用了Cal函数并在一段时间后输出了结果,同时Main函数并不会被阻塞。
分别在Main、Cal、CalAsync函数中增加代码打印当前线程ID:
class Test
{static void Main(string[] args){string tid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine($"Main1 tid {tid}");Task<int> t = CalAsync();Console.WriteLine($"Main after CalAsync");Console.Read();}public static int Cal(){string tid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine($"Cal tid {tid}");int sum = 0;for (int i = 0; i < 999; i++){sum = sum + i;}Console.WriteLine($"sum={sum}");return sum;}public static async Task<int> CalAsync(){string tid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine($"CalAsync1 tid {tid}");int result = await Task.Run(new Func<int>(Cal));tid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine($"CalAsync2 tid {tid}, result={result}");return result;}
}
结果如图:
可以看出,在CalAsync函数中,await标记之前,代码在主线程中执行,而await标记之后,代码在子线程中执行。
理解与结论:
-
在C#中, async标记了一个包含异步执行的函数,通过async标记的函数若在主线程中直接调用,则函数一开始仍在主线程中执行;
-
aysnc标记的函数内部必须包含await标记需要异步执行的函数(根据vs2017编译提示),若当前函数在主线程中直接调用,则await标记前的代码在主线程中执行,await标记后的代码在其异步子线程中执行;
-
async标记的函数返回值必须为void、Task、Task< TResult> 类型,可以理解为async标记的函数返回的是 “空”、“即将执行的任务”、“带结果的即将执行的任务”实例;
-
async标记的函数可以继续往下调用async标记函数,调用形式如下例, 从调用逻辑可以理解为await实际上用来触发所标记的Task任务异步执行,并最后获取异步执行的返回值,从运行过程看该触发应该仅对最终的Task任务有效:
public static async Task<int> CallCalAsync(){string tid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine($"CallCalAsync1 tid {tid}");int result = await CalAsync();tid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine($"CallCalAsync2 tid {tid}, result={result}");return result;}
总结
C#中async与await异步编程,可以理解为:
1. async声明了一个包含异步执行代码的函数,该函数执行时不会阻塞调用线程;
2. await存在于async函数中,声明了一个异步执行入口,程序动态运行时从该入口创建并进入一个异步线程环境,并在该线程执行任务实例及任务实例返回之后的代码;
3. 一个async函数中声明多个await关键字时,程序将代码顺序创建并进入异步子线程执行任务实例及任务实例返回之后的代码直到下一个await声明处, 最后一个await声明之后的代码会在最后一个异步子线程中执行 ;
3. await标记的右侧代码返回或定义了一个任务实例,该实例由需要异步执行的目标耗时函数初始化,并在最终定义处触发异步执行。