文章目录
async和await
为了简化异步编程,摒弃复杂的流控制,提高异步编程的可读性,C# 5引入了async和await关键字。它们的实现基于Task类,将异步操作变为一种语言特性。使得异步变得像同步一样易读。
用法
async:
- 声明异步方法
- 使用await,需要用async修饰await所在的方法
await:
- 等待异步方法执行完毕但不会阻塞主线程
- 将async方法分为两部分,await前的为async方法执行的主线程,await之后的部分在子线程中执行
//模拟一个异步操作
//我做饭,发现没盐了,叫老婆去买盐
//我(主线程)继续做饭,老婆(子线程)去买盐
static void Main(string[] args)
{
ShowCurrentThreadID($"CookThread");
// 调用异步方法,主线程会进入该方法
Task<string> saltStatu = BuySaltAsync();
// 主线程继续做饭
Console.WriteLine("Cooking..");
// 如果用到异步方法的返回结果,阻塞在这里,等待异步执行完毕,获得有效返回值
if (saltStatu.Result == "Salt is ready")
Console.WriteLine("Add Salt");
Console.WriteLine("A feast is ready!");
Console.ReadKey();//防止程序直接退出
}
static async Task<string> BuySaltAsync()
{
ShowCurrentThreadID("CallForWife");
// 叫老婆去买盐,自己回去继续做饭(创建一个子线程)
string res = await Task<string>.Run(() =>
{
ShowCurrentThreadID(" WifeShopping");
Thread.Sleep(100);
return "Salt is ready";
});
ShowCurrentThreadID(" SaltGoHome");
// 盐买回家,更新Flag
return res;
}
// 获得当前线程ID
static void ShowCurrentThreadID(string threadTag)
{
Console.WriteLine($"{threadTag} ID is {Thread.CurrentThread.ManagedThreadId.ToString()}");
}
运行结果:
上面的例子看起来似乎是await创建了一个子线程,但并不是,实际上是Task.Run()创建了一个子线程。在它们之间加点延时,把异步方法修改下:
static async Task<string> BuySaltAsync()
{
ShowCurrentThreadID("CallForWife");
Task<string> task = Task<string>.Run(() =>
{
ShowCurrentThreadID(" WifeShopping");
Thread.Sleep(100);
return "Salt is ready";
});
Thread.Sleep(50);
ShowCurrentThreadID(" WaitForWife");
string res = await task;
ShowCurrentThreadID(" SaltGoHome");
return res;
}
运行结果:
Task.Run()创建了一个子线程,并且主线程遇到await才退出异步方法。
那如果在子线程完成才使用await会发生什么?我们把延时加到200ms,如下
static async Task<string> BuySaltAsync()
{
ShowCurrentThreadID("CallForWife");
Task<string> task = Task<string>.Run(() =>
{
ShowCurrentThreadID(" WifeShopping");
Thread.Sleep(100);
return "Salt is ready";
});
Thread.Sleep(200);
ShowCurrentThreadID(" WaitForWife");
string res = await task;
ShowCurrentThreadID(" SaltGoHome");
return res;
}
运行结果:
现在连异步方法的后半部分即await后面的代码也由主线程执行了。这说明,在子线程执行过程中,await会把后面的部分作为“回调函数”给子线程附加,此例中子线程已经结束被回收,就没法再在子线程中运行回调。
await等待的是Task任务执行的结果:一个Task类型的返回值,那可不可以再等待一个返回值为Task的异步方法呢?试试:
static async Task<string> BuySaltAsync()
{
ShowCurrentThreadID("CallForWife");
// 老婆不想去买,叫儿子去
string res = await CallSonBuySaltAsync();
ShowCurrentThreadID(" SaltGoHome");
return res;
}
static async Task<string> CallSonBuySaltAsync()
{
ShowCurrentThreadID("CallForSon");
string res = await Task<string>.Run(() =>
{
ShowCurrentThreadID(" SonShopping");
Thread.Sleep(100);
return "Salt is ready";
});
return res;
}
运行结果:
在上述嵌套await中,主线程遇到第一个await并没有返回,而是继续往下执行嵌套的异步方法,在这个子线程创建后,主线程执行到await,await通知主线程已经有一个子线程了,主线程才退出异步方法。
小结
-
await等待异步方法执行完毕且不会阻塞主线程,await不创建线程,
-
await会把异步方法后面的部分作为“回调函数”给子线程附加
-
await将异步方法的返回值Task拆包为T类型变量
-
如果子线程有返回值,并且在主线程中被调用,主线程会阻塞在返回值的调用部分