深入理解 C# .NET Core 中 async await 异步编程思想
引言
很久没来CSDN了,快小半年了一直在闲置,主要是因为一时写不出一些稍微带有思想和深度的文章;之前就写过一篇关于async await 的异步理解 ,现在回顾,描述的并不到位、理解得也不透彻!好了,废话不多说了,直入主题吧!
一、什么是异步?
我们采用最简单的方式来描述吧,如同:当我们回到家打开电视机后,我们同时还可以烧热水,煮杯咖啡!当咖啡煮好了,好看的电视剧也开始了,在等待好看的电视进行播放和煮咖啡这个过程是同时执行的,所以我们才能在电视播放的同时一边喝咖啡一边品好剧;这种高效的处理方式,我们把它叫做异步!官方实例(特别生动形象的一个例子)
1.1 简单实例(WatchTV并行CookCoffee)
// See https://aka.ms/new-console-template for more information
var watchTV = WatchTV();//TV
var cookCoffee=CookCoffee();//Coffee
Console.ReadKey();
/// <summary>
/// TV
/// </summary>
static Task WatchTV()
{
return Task.Run(() =>
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:脑白金广告开始了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:脑白金广告结束了,大宝SOD密广告开始了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:大宝SOD密广告结束了,好剧开始了!");
});
}
/// <summary>
/// Coffee
/// </summary>
static Task CookCoffee()
{
return Task.Run(() =>
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:开始烧水了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:水烧开了,开始煮咖啡了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:咖啡煮好了,好剧开始了,准备一边喝咖啡一边品剧了!");
});
}
注意看,最巧妙的地方就在于,相同的时间,广告播放完了,而我的咖啡也煮好了!异步,实质上就是相同的时间我可以同时完成很多件事,就像我们煮咖啡的时候,什么爱奇艺、优酷、腾讯…讨厌的大段广告已经播放完了呀,简直不要太nice,yyds!
二、深入理解(异步)
2.1 当我需要异步返回值时,怎么处理?
简单,通过awiat,即可得到我们想要的运行结果,那我们简单改造下代码,加个返回值:
var watchTV = await WatchTV();//TV
Console.WriteLine(watchTV);
var cookCoffee=await CookCoffee();//Coffee
Console.WriteLine(cookCoffee);
Console.ReadKey();
static Task<string> WatchTV()
{
return Task.Run(() =>
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:脑白金广告开始了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:脑白金广告结束了,大宝SOD密广告开始了!");
Thread.Sleep(2000);
return $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:大宝SOD密广告结束了,好剧开始了!";
});
}
static Task<string> CookCoffee()
{
return Task.Run(() =>
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:开始烧水了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:水烧开了,开始煮咖啡了!");
Thread.Sleep(2000);
return $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:咖啡煮好了,好剧开始了,准备一边喝咖啡一边品剧了!";
});
}
这里注意了,怎么感觉好像回到了同步,因为这时候,好像已经不再是异步,它在一步一步地执行,明明是异步的写法,怎么成了同步的效果,这到底怎么回事?
其实,官方早已有了解释,也就是说,这时候虽然有了await关键字,线程是不会阻塞,但实际耗时却和同步是一样,但是要注意,这时候任然是异步,千万别理解成回归同步了,因为只是没有利用异步的关键功能!
2.2 充分利用异步并行的高效性
我们继续进行代码改造,因为上述的操作只是因为没有同时进行任务启动,那我们就改成同时进行任务启动吧!这样,就得到我们想要的结果了,既异步并行了,我们又拿到了自己想要返回值,简直不要太爽!
// See https://aka.ms/new-console-template for more information
var watchTv=WatchTV();//TV
var cookCoffee=CookCoffee();//Coffee
Console.WriteLine(await watchTv);
Console.WriteLine(await cookCoffee);
Console.ReadKey();
三、 async await 的秘密
注意看下面这段代码和运行结果,怎么好像主线程Id怎么被切换成子线程Id了呢?
// See https://aka.ms/new-console-template for more information
Console.WriteLine("MainId:" + Thread.CurrentThread.ManagedThreadId);
var watchTv = WatchTV();//TV
var cookCoffee = CookCoffee();//Coffee
Console.WriteLine(await watchTv);
Console.WriteLine(await cookCoffee);
Console.WriteLine("MainId:" + Thread.CurrentThread.ManagedThreadId);
Console.ReadKey();
static Task<string> WatchTV()
{
return Task.Run(() =>
{
Console.WriteLine("Tv:" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:脑白金广告开始了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:脑白金广告结束了,大宝SOD密广告开始了!");
Thread.Sleep(2000);
return $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:大宝SOD密广告结束了,好剧开始了!";
});
}
static Task<string> CookCoffee()
{
return Task.Run(() =>
{
Console.WriteLine("Coffee:" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:开始烧水了!");
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:水烧开了,开始煮咖啡了!");
Thread.Sleep(2000);
return $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:咖啡煮好了,好剧开始了,准备一边喝咖啡一边品剧了!";
});
}
这是怎么回事呢?让我们来仔细分析分析?起初我怎么都想不明白,后来终于在 官网 找到答案,最简单直白地说:当异步 async await 遇到 await 时,异步任务会移交控制权,让步于调用方,await 异步任务完成后,但Main(主线程)还没做完呀,仍在异步线程中,答案:“异步任务执行运算的同时,允许其他任务在当前线程执行”,所以这时候当前线程成了异步线程Id自然就一点都不奇怪了!