浅析 C# 异步编程:async 和 await
摘要:本文通过生活场景引入同步与异步编程概念,详细讲解了C#中async/await的工作原理。作者首先用同步代码示例展示阻塞式等待,然后将其改造为异步版本,重点解析await如何在不阻塞线程的情况下暂停和恢复方法执行。文章还提供了一个完整的异步"点外卖"示例,对比同步和异步实现的时间效率差异,并澄清了"await等待的是可等待表达式而非async方法"的常
本文我们从一个生活中的场景为例开始 ,探究 C# 的 async / await
B站同名账号KrnlsYs有本文的视频版讲解,还有C#零基础4小时速通入门教程,读者可按需观看
一、从生活中的场景理解同步和异步
先来设想一个场景:我饿了,要吃午饭,于是点了一份外卖。并且我还打算看视频学习C#
同步思维
-
点外卖
-
站在门口,什么也不干,一直等
-
半个小时后,外卖到了
-
开始吃饭,吃完
-
吃完之后,才开始学习 C#
这个流程是正确的,逻辑也很清晰:一步一步来,前一步不完成,后一步就不开始
但实际上,正常人点了外卖之后不会干等着,而是去做其他的事情
异步思维
更合理的方式是:
-
点外卖
-
在等外卖的过程中,开始学习 C#
-
外卖到了,边吃边继续看
-
吃完,学习也差不多结束了
做了同样的事,总耗时明显减少了
二、引入async和await
先来看一段完全同步的代码:
namespace KrnlsYs
{
class Program
{
static void Show()
{
Console.WriteLine(2);
Thread.Sleep(3000);
Console.WriteLine(3);
}
static void Main(string[] args)
{
Console.WriteLine(1);
Show();
Console.WriteLine(4);
Console.ReadLine();
}
}
}
这段代码非常简单,先输出1 2 三秒后输出 3 4
Thread.Sleep 是同步(阻塞)的等待。不理解没关系,下文会和异步(不阻塞)对比
现在把Show方法改成异步版本:
static async void ShowAsync()
{
Console.WriteLine(2);
await Task.Delay(3000);
Console.WriteLine(3);
}
注意:
- 将Thread.Sleep修改为 await Task.Delay(3000);
- 方法使用async关键字修饰,并且方法名称加上Async(异步方法的命名约定)
- 绝大部分情况下不应该用void作为异步方法的返回值,如果不需要返回值应该使用 Task,需要额外返回值使用 Task<TResult>,本文暂不讨论
运行后你会看到:1 2 4 三秒后输出 3
三、await 到底做了什么
真正的关键,就在:await Task.Delay(3000);
await 的行为可以分成几步理解:
-
先判断:后面的这个 "东西" 是不是已经完成了
-
如果已经完成:像同步代码一样,继续往下执行
-
如果没有完成(需要等待)
-
暂停当前方法
-
把控制权还给调用方
-
当前线程不阻塞,可以去干别的事
-
等 Task.Delay(3000) 真的完成之后:
-
运行时会“通知”回来
-
再继续执行
await后面的代码
所以现在很好解释刚才的输出:
-
Main输出1 -
调用
ShowAsync,输出2 -
遇到
await,方法暂停,直接返回 -
Main继续执行,输出4 -
3 秒后,
ShowAsync恢复执行,输出3
所以最终顺序是:1243
四、一个“点外卖”的完整异步示例
我们把最开始的点外卖的例子用代码实现
using System.Diagnostics;
namespace KrnlsYs
{
class Program
{
static void OrderDelivery()
{
Console.WriteLine("Ordering delivery");
}
static void WaitForDelivery()
{
Console.WriteLine("Waiting...");
Thread.Sleep(5000);
Console.WriteLine("Delivery arrived");
}
static void Eat()
{
Console.WriteLine("Eating...");
Thread.Sleep(5000);
Console.WriteLine("Finished eating");
}
static void LearnCSharp()
{
Console.WriteLine("Learning...");
Thread.Sleep(10000);
Console.WriteLine("Finished learning C#");
}
static void Main(string[] args)
{
var sw = Stopwatch.StartNew();
OrderDelivery();
WaitForDelivery();
Eat();
LearnCSharp();
sw.Stop();
Console.WriteLine(sw.Elapsed.TotalSeconds);
}
}
}
同步版本:总耗时 20 秒 = 等外卖 5 秒 + 吃饭 5 秒 + 学习 C# 10 秒
异步版本:并行等待
using System.Diagnostics;
namespace KrnlsYs
{
class Program
{
static void OrderDelivery()
{
Console.WriteLine("Ordering delivery");
}
static async Task WaitForDeliveryAsync()
{
Console.WriteLine("Waiting...");
await Task.Delay(5000);
Console.WriteLine("Delivery arrived");
}
static async Task EatAsync()
{
Console.WriteLine("Eating...");
await Task.Delay(5000);
Console.WriteLine("Finished eating");
}
static async Task LearnCSharpAsync()
{
Console.WriteLine("Learning...");
await Task.Delay(10000);
Console.WriteLine("Finished learning C#");
}
static async Task Main(string[] args)
{
var sw = Stopwatch.StartNew();
OrderDelivery();
var waitingTask = WaitForDeliveryAsync();
var learningTask = LearnCSharpAsync();
await waitingTask;
await EatAsync();
await learningTask;
sw.Stop();
Console.WriteLine(sw.Elapsed.TotalSeconds);
}
}
}
等外卖的同时开始学习,外卖到后开始吃,吃完时,学习也刚好完成,总耗时10秒
五、一个非常重要的误区:await 等的是什么?
很多人会误以为:只有 async 方法才能被 await,这是错误的
例如如下代码 await Show(); 行错误,因为Show无法被await
namespace KrnlsYs
{
class Program
{
static async void Show()
{
}
static async Task Main(string[] args)
{
await Show();
}
}
}
正确的说法是:await 等待的是一个awaitable expression 可等待表达式,因此不论方法是不是async,只要返回的可等待的,那么就可以await
如下代码的Show没有被async修饰,但是可以await
namespace AsyncExample
{
class Program
{
static Task Show() => new(() => { });
static async Task Main(string[] args)
{
await Show();
}
}
}
可以简单理解为:
- async 的作用,只是为了让方法内部可以使用 await
- 真正的异步行为,发生在 await 上,而不是 async 上
更多推荐



所有评论(0)