前言

相信大家看过不少讲C# async await的文章,博客园就能搜到很多,但还是有很多C#程序员不明白。

如果搞不明白,其实也不影响使用。但有人就会疑惑,为什么要用异步?我感觉它更慢了,跟同步有啥区别?

有的人研究深入,比如去研究状态机,可能会明白其中的原理。但深入研究的毕竟少数。有的人写一些示例,有的专业有的不是很专业,但看的人仍有不明白的。

所以我写了一个示例,尽量简单,让大家形象地理解一下C#的异步。

就是一个示例,没有什么理论讲解,大家自己测试自己思考。如果大家看理论讲解,就能明白,我也就没必要写这个示例了。

示例的测试步骤

先直接测试,看结果,下面再放代码

  1. 点击VS2022的启动按钮,启动程序,它会先启动Server工程,再启动AsyncAwaitDemo2工程
  2. 快速连续点击5次button1(调用的是同步方法)
  3. 快速连续点击5次button2(调用的是异步方法)
  4. 观察思考输出结果,体会异步的好处

测试截图

测试截图说明

  1. 5次点击button1,界面是卡住的,但不影响输出结果,5次点击button2界面不会卡住(截图看不出来,测试过程中有体现)
  2. 5次点击button1,共5次同步请求,每个请求耗时2秒,一共耗时10秒,嫌慢,想要并发请求?那你可能要使用Task.Run,在线程中调用同步方法,这又涉及到线程占用问题
  3. 5次点击button2,共5次异步请求,每个请求耗时2秒,但一共耗时只有2秒,注意,代码中请求web api接口使用Thread类了吗?没有!使用Task.Run了吗?没有!使用了async await语法糖,是不是比通过new Thread或Task.Run要简单多了?就像写同步代码那样,却实现了异步并发的效果。

一个问题

其它博客也写过示例,是控制台示例,输出的主线程ID,是有可能会变化的。这里是Winform示例,输出的主线程ID始终是UI线程的ID,即是1;

我没有演示主线程ID发生变化的情况,这样会使示例变得复杂一点,不容易看明白。

服务端

服务端和客户端是两个独立的工程,测试时在一起跑,但其实可以分开部署,部署到不同的机器上

服务端是一个web api接口,用.NET 6、VS2022开发,代码如下:

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
[HttpGet]
[Route("[action]")]
public async Task<string> ForPreheat()
{
return await Task.FromResult("预热返回结果");
} [HttpGet]
[Route("[action]")]
public async Task<string> Get()
{
await Task.Delay(2000); //模拟耗时操作
return "测试返回结果";
}
}

客户端

大家看客户端代码时,不需要关心服务端怎么写

客户端是一个Winform工程,用.NET 6、VS2022开发,代码如下:

public partial class Form1 : Form
{
private string _urlPreheat = "http://localhost:5028/Test/ForPreheat";
private string _url = "http://localhost:5028/Test/Get";
private int _index1 = 1; //测试时请快速连续点击5次button1
private int _index2 = 1; //测试时请快速连续点击5次button2
private Stopwatch _sw1;
private Stopwatch _sw2; public Form1()
{
InitializeComponent();
} private async void Form1_Load(object sender, EventArgs e)
{
Request(_urlPreheat); //预热
await RequestAsync(_urlPreheat); //预热
Log("==== 预热请求结束 ====================================");
} #region Log
private void Log(string msg)
{
msg = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {msg}\r\n"; if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
txtLog.AppendText(msg);
}));
}
else
{
txtLog.AppendText(msg);
}
}
#endregion private async void button1_Click(object sender, EventArgs e)
{
await Task.Delay(1000); //为了能1秒内快速点击几次,为了能写这句代码button1_Click前面加了async,但后面的代码仍是同步调用;如果不加这句,第一次点击界面就卡死了,无法连续点击5次。 Log("==== 同步请求开始 ====================================");
if (_index1 == 1) _sw1 = Stopwatch.StartNew(); Request(_url); if (_index1 == 5)
{
_sw1.Stop();
Log($"5次请求耗时:{_sw1.Elapsed.TotalSeconds:0.000}秒");
}
_index1++;
} private async void button2_Click(object sender, EventArgs e)
{
await Task.Delay(1000); //为了和button1保持一致 Log("==== 异步请求开始 ====================================");
if (_index2 == 1) _sw2 = Stopwatch.StartNew(); await RequestAsync(_url); if (_index2 == 5)
{
_sw2.Stop();
Log($"5次请求耗时:{_sw2.Elapsed.TotalSeconds:0.000}秒");
}
_index2++;
} private async Task RequestAsync(string url)
{
Stopwatch sw = Stopwatch.StartNew();
HttpClient httpClient = HttpClientFactory.GetClient();
var result = await (await httpClient.GetAsync(url)).Content.ReadAsStringAsync();
sw.Stop();
Log($"线程ID={Thread.CurrentThread.ManagedThreadId},请求耗时:{sw.Elapsed.TotalSeconds:0.000}秒,返回内容长度:{result.Length}");
} private void Request(string url)
{
Stopwatch sw = Stopwatch.StartNew();
var result = HttpUtil.HttpGet(url);
sw.Stop();
Log($"线程ID={Thread.CurrentThread.ManagedThreadId},请求耗时:{sw.Elapsed.TotalSeconds:0.000}秒,返回内容长度:{result.Length}");
}
}

思考

把button2_Click方法中的await RequestAsync(_url);修改为两行:

await RequestAsync(_url);
await RequestAsync(_url);

那么5次点击,每次点击请求2次,共5×2=10次请求,请问这10次请求的总耗时大约是几秒?

答案是4秒,为什么?

因为每次点击的2次请求是顺序的,耗时是2+2=4秒,而5次点击是并发的,没有增加耗时时长

Demo完整代码

https://gitee.com/s0611163/AsyncAwaitDemo2

流程图

最后,我画了两个流程图,但这两个流程图可能画的很不专业,所以仅供参考

button1点击5次的流程图:



button2点击5次的流程图:



我之前说过,异步的本质是回调,这句话是否正确我也不知道,主要是为了大家理解异步。

通过一个示例形象地理解C# async await异步的更多相关文章

  1. 深入理解协程(四):async/await异步爬虫实战

    本文目录: 同步方式爬取博客标题 async/await异步爬取博客标题 本片为深入理解协程系列文章的补充. 你将会在从本文中了解到:async/await如何运用的实际的爬虫中. 案例 从CSDN上 ...

  2. 【转】C# Async/Await 异步编程中的最佳做法

    Async/Await 异步编程中的最佳做法 Stephen Cleary 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支 ...

  3. 将 async/await 异步代码转换为安全的不会死锁的同步代码

    在 async/await 异步模型(即 TAP Task-based Asynchronous Pattern)出现以前,有大量的同步代码存在于代码库中,以至于这些代码全部迁移到 async/awa ...

  4. .NET Web应用中为什么要使用async/await异步编程

    前言 什么是async/await? await和async是.NET Framework4.5框架.C#5.0语法里面出现的技术,目的是用于简化异步编程模型. async和await的关系? asy ...

  5. C#中 Thread,Task,Async/Await 异步编程

    什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调 ...

  6. async/await异步处理demo

    async/await异步处理demo 下载地址: async/await异步处理demo

  7. 理解 es7 async/await

    简介 JavaScript ES7 中的 async / await 让多个异步 promise 协同工作起来更容易.如果要按一定顺序从多个数据库或者 API 异步获取数据,你可能会以一堆乱七八糟的 ...

  8. async/await 异步编程

    前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入的,由于之前对于异步编程不是很了解,所以花费了一些时间学习一下相关的知识,并整理成这篇博客,如果在 ...

  9. c# 关于async/await异步编程的浅析和使用

    线程的同步运行,时效性慢,异步运行,时效性快! 在c#5.0引出了async/await关键字,可以用其来进行异步编程. async/await定义异步方法的语法如下: 1.在方法的返回类型前面加上a ...

  10. Async await 异步编程说明

    希望在编程上有些许提高所以 最近连续2篇博客都在说明多线程和异步编程的使用,异步和多线程之间区别请自行百度,因为理解不是特别透彻就不在叙述以免误导大家,这里写下新研究整理 task  和 await ...

随机推荐

  1. CSS 属性选择器 ~=, |=, ^=, $=, *= 的区别

    CSS 属性选择器 ~=, |=, ^=, $=, *= 的区别 总结: "value 是完整单词" 类型的比较符号: ~=, |= "拼接字符串" 类型的比较 ...

  2. C#中ref和out关键字的应用以及区别(参数修饰符)

    ref ref的定义 ref是reference的缩写,通过引用来传递参数的地址,ref基本上是服务于值类型的 ref的使用 //不使用 ref; void Method(int myRefInt) ...

  3. Mockito使用方法(Kotlin)

    一.为什么要使用Mockito 1.实际案例 1.1 遇到的问题 对于经常维护的项目,经常遇到一个实际问题:需求不停改变,导致架构经常需要修改某些概念的定义. 对于某些十分基础又十分常用的概念,常常牵 ...

  4. Python基础之面向对象:3、继承与派生

    面向对象 一.三大特征之继承 python三大特征: 封装.继承.多态 三者中继承最为核心,实际应用对,感受较为直观 封装和多态略微抽象 1.继承的概念 继承的含义: ​ 在现实生活中,继承表示人与人 ...

  5. Python基础之面向对象:1、面向对象及编程思想

    一.人狗大战 1.需求 用代码模拟人.狗打架的小游戏 人和狗种类不同,因此双方的属性各不相同 推导一: 人和狗各有不同属性 使用字典方式储存属性较为方便,并可储存多种属性 # 1.在字典内储存'人'属 ...

  6. springcloud组件梳理之Feign

    最近刚好打算做一个springcloud系列的分享,趁此机会刚好梳理下springcloud常用组件的使用,今天先对feign做个简单介绍! feign是一个声明式的Web服务客户端,它使得发送web ...

  7. centos8安装vsftpd

    注:ftp只能走相对路径传输文件,需要先cd到文件路径,然后ftp登陆,put上传,get下载 1. 装包与卸载 yum -y install vsftpd yum -y autoremove vsf ...

  8. 【云原生 · Kubernetes】部署zookeeper

    个人名片: 因为云计算成为了监控工程师‍ 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying 部署zookeeper 1.1 zookeeper概述 1.2 ZooKeeper服务中操作 ...

  9. 网络编程:多进程实现TCP服务端并发、互斥锁代码实操、线程理论、创建线程的两种方式、线程的诸多特性、GIL全局解释器锁、验证GIL的存在

    目录 多进程实现TCP服务端并发 互斥锁代码实操 线程理论 创建线程的两种方式 线程的诸多特性 GIL全局解释器锁 验证GIL的存在 GIL与普通互斥锁 python多线程是否有用 死锁现象 多进程实 ...

  10. 【Java面试指北】Exception Error Throwable 你分得清么?

    读本篇文章之前,如果让你叙述一下 Exception Error Throwable 的区别,你能回答出来么? 你的反应是不是像下面一样呢? 你在写代码时会经常 try catch(Exception ...