谈谈对.NET中async/await的理解
一、什么是同步?什么是异步? 在 .NET 的世界里,async 和 await 这两个关键字可以说是异步编程的门面担当。async 用来标记一个方法是异步方法,里面可以藏着多个 await 表达式;而 await 呢,就是专门在异步方法里等待某个任务(Task 或 Task)完成的。当程序执行到
一、什么是同步?什么是异步?
在 .NET 的世界里,async 和 await 这两个关键字可以说是异步编程的门面担当。async 用来标记一个方法是异步方法,里面可以藏着多个 await 表达式;而 await 呢,就是专门在异步方法里等待某个任务(Task 或 Taskawait 时,当前方法会暂停,但主线程并不会傻等着——它会转身去干别的事,等那个任务完成了再回来继续跑剩下的代码。
那同步和异步到底怎么区分?
- 同步:一个方法被调用,调用者必须老老实实等到它执行完,才能接着往下走。这就好比排队买奶茶,前面的人不取走,后面的人就干等着。
- 异步:方法一被调用就立即返回,然后启动一个线程(Task)去处理它内部的逻辑,调用者根本不用等。这就相当于你点了外卖,然后该干嘛干嘛,外卖做好了自然会送到你手上。
二、async/await 是怎么提高性能的?
异步最大的好处就是非阻塞——调用线程不会停下来干等子线程完成。所以,那些不急着要结果、但是很耗时的任务,完全可以扔到异步里去跑,整个程序的效率自然就上来了。
举个直观的例子:主线程要依次执行 5 个方法,耗时分别是 0.1s、0.2s、0.3s、0.4s、0.5s。如果用同步的方式,主线程得老老实实一个一个串着跑,总时间就是 1.5 秒。但如果把这些方法都改成异步的,主线程大概只需要 0.5 秒就能搞定。
原因很简单:同步就像一根线穿五颗珠子,一颗穿完才能穿下一颗,总时间就是所有珠子时间之和。异步呢,相当于五个人每人拿一根线同时穿一颗珠子,总时间取决于最慢的那颗珠子——0.5 秒。因为主线程调用异步方法时,不会一头扎进去执行,而是在遇到 await 后立刻返回一个 Task,这个 Task 会在子线程里慢慢跑,主线程则继续往后走,去调用下一个方法。
那问题来了:主线程跑得飞快,异步方法的结果怎么收回来?常见的方式有这么几种:
1. 直接用 await 等待
public class Demo
{
public async Task DemoAsync()
{
await Task.Delay(1000);
Console.WriteLine("1秒后执行");
}
}
public Class Program
{
static async Task Main(string[] args)
{
Demo demo = new Demo();
// 使用 await 等待DemoAsync的执行
await demo.DemoAsync();
Console.ReadKey();
}
}
2. 使用事件通知
internal class Program
{
static void Main(string[] args)
{
Demo demo = new Demo();
demo.OnEvent += (() =>
{
Console.WriteLine("事件订阅");
});
demo.DemoAsync();
Console.ReadKey();
}
}
public class Demo
{
public event Action OnEvent;
public async Task DemoAsync()
{
Console.WriteLine("开始执行");
await Task.Delay(1000);
Console.WriteLine("1秒后执行");
OnEvent?.Invoke();
}
}
3. 回调函数
internal class Program
{
static void Main(string[] args)
{
Demo demo = new Demo();
demo.DemoAsync(() =>
{
Console.WriteLine("回调函数");
});
Console.ReadKey();
}
}
public class Demo
{
public async Task DemoAsync(Action callback)
{
Console.WriteLine("开始执行");
await Task.Delay(1000);
callback?.Invoke();
Console.WriteLine("1秒后执行");
}
}
4. 只启动异步操作,不等待结果
如果只是启动一个异步任务,根本不需要它的返回值,那就直接调用,不用加
await。主线程不会被阻塞,也不会去管结果。
internal class Program
{
static void Main(string[] args)
{
Demo demo = new Demo();
demo.DemoAsync();
Console.ReadKey();
}
}
public class Demo
{
public async Task DemoAsync()
{
Console.WriteLine("开始执行");
await Task.Delay(1000);
Console.WriteLine("1秒后执行");
}
}
5. 把异步结果存到变量里,后面再取
如果想在某个时刻拿到异步结果,但又不想立刻等它完成,可以先把返回的 Task 存起来,等需要的时候再用
Wait()或.Result来取。
internal class Program
{
static void Main(string[] args)
{
Demo demo = new Demo();
var demoResult = demo.DemoAsync();
// 使用 wait等待异步的完成
demoResult.Wait();
if (demoResult.IsCompleted)
{
Console.WriteLine("str执行完成");
Console.WriteLine(demoResult.Result);
}
Console.ReadKey();
}
}
public class Demo
{
public async Task DemoAsync()
{
Console.WriteLine("开始执行");
await Task.Delay(1000);
Console.WriteLine("1秒后执行");
return "异步返回结果";
}
}
三、异步到底解决了什么?到底起到了什么样的作用?
- 提高响应性:用
async和await可以避免那些长时间运行的操作(比如 IO 操作)把主线程卡死,应用程序的反应自然更快了。 - 简化异步代码:写异步代码现在几乎和写同步代码一样直白,出错概率也大大降低。
- 优化资源使用:线程在等待任务完成时会被释放出来,跑去服务别的任务,而不是傻等——资源利用率一下就上去了。
四、在使用异步时的一些问题的解决
1、异步的传递性问题
异步的效果会沿着调用链一路向上传染。你调了个异步方法,那调用它的方法也得变成异步,而且返回类型还得改成 Task 或 Task
那怎么避免这种“强制传染”呢?有几种思路:
- 不用 await 直接调用:如果不需要异步方法的结果,可以直接调用,不加
await,这样调用者就不用变成异步方法了。 - 用 Task.Run 来启动异步操作:
Task.Run会新起一个任务去跑异步逻辑,从而切断了传递性。 - 用事件或回调来处理结果:如果只需要在完成时得到通知,事件或回调比直接
await更灵活,也不会逼迫上层变成异步。 - 把异步结果存起来,后面再等:将 Task 赋值给一个变量,等需要结果时再调用
Wait()或访问.Result,虽然不优雅但确实能避开传递性。
WPS Office是一款面向macOS用户的综合办公套件,将文字处理、电子表格、演示文稿、PDF工具、模板与云协作整合在同一应用中。它兼容常见Microsoft Office格式,支持Intel及Apple芯片Mac,基础功能可免费使用,
Shapr3D是一款面向工业设计、机械工程、建筑概念和三维打印工作流的CAD软件。Mac版采用Parasolid建模内核,支持草图约束、实体建模、工程图、可视化渲染及常见CAD格式交换,并可通过账户在多台设备之间同步项目。
REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。
Adobe Photoshop 是面向摄影师、设计师和内容创作者的专业图像处理工具,可在 Windows 上完成照片修饰、选区抠图、图层合成、文字排版、绘画及生成式编辑。软件采用订阅授权,由 Creative Cloud 管理安装、更新与许
Adobe InDesign 是面向印刷与数字出版的专业页面排版工具,可制作书籍、杂志、宣传册、海报、交互式 PDF 和电子书。它提供精细的文字样式、多页面管理、印前检查及多格式输出能力,并能与 Photoshop、Illustrator
Adobe Illustrator 是用于绘制矢量图形、插画、图标、徽标和版式的专业设计工具。它以路径和锚点为核心,作品放大后仍能保持清晰,并提供画板、文字、渐变、图像描摹及精确变换等功能,适合品牌视觉、印刷物料、数字界面和商业插画创作。













