C#进阶——从应用上理解异步编程的作用(async / await)
欢迎来到学习摆脱又加深内卷篇
下面是学习异步编程的应用
1.首先,我们建一个winfrom的项目,界面如下:

2.然后先写一个耗时函数:
/// <summary>
/// 耗时工作
/// </summary>
/// <returns></returns>
private string Work()
{
Thread.Sleep(1000);
Thread.Sleep(2000);
//listBox1.Items.Add("耗时任务完成");
return DateTime.Now.ToString("T") + "进入耗时函数里, 线程ID:" + Thread.CurrentThread.ManagedThreadId; //步骤7:子线程运行,不阻塞主线程
}
这里用当前线程睡眠来模拟耗时工作
3.同步实现方式:
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Add(DateTime.Now.ToString("T") + "调用异步之前,线程ID:" + Thread.CurrentThread.ManagedThreadId); //步骤1:在主线程运行,阻塞主线程
TaskSync();
listBox1.Items.Add(DateTime.Now.ToString("T") + "调用异步之后,线程ID:" + Thread.CurrentThread.ManagedThreadId); //步骤2:在主线程运行,阻塞主线程
} /// <summary>
/// 同步任务
/// </summary>
private void TaskSync()
{
listBox1.Items.Add(DateTime.Now.ToString("T") + "同步任务开始,线程" + Thread.CurrentThread.ManagedThreadId);
var resual = Work();
listBox1.Items.Add(resual);
listBox1.Items.Add(DateTime.Now.ToString("T") + "同步任务结束,线程" + Thread.CurrentThread.ManagedThreadId);
}
运行结果:

很明显以上就是同步实现方法,在运行以上代码时,会出现UI卡住了的现象,因为耗时工作在主线程里运行,所以UI一直刷新导致假死。
4.那么我们就会想到,可以开一个线程运行耗时函数,比如:
private void button4_Click(object sender, EventArgs e)
{
listBox1.Items.Add(DateTime.Now.ToString("T") + "独立线程之前,线程" + Thread.CurrentThread.ManagedThreadId);
ThreadTask();
listBox1.Items.Add(DateTime.Now.ToString("T") + "独立线程之后,线程" + Thread.CurrentThread.ManagedThreadId);
} /// <summary>
/// 接收线程返回值
/// </summary>
class ThreadParm
{
/// <summary>
/// 接收返回值
/// </summary>
public string resual = "耗时函数未执行完"; /// <summary>
/// 线程工作
/// </summary>
/// <returns></returns>
public void WorkThread()
{
resual = Work();
} /// <summary>
/// 耗时工作
/// </summary>
/// <returns></returns>
private string Work()
{
Thread.Sleep(1000);
Thread.Sleep(2000);
//listBox1.Items.Add("耗时任务完成");
return DateTime.Now.ToString("T") + "进入耗时函数里, 线程ID:" + Thread.CurrentThread.ManagedThreadId; //步骤7:子线程运行,不阻塞主线程
}
} /// <summary>
/// 独立线程任务
/// </summary>
private void ThreadTask()
{
listBox1.Items.Add(DateTime.Now.ToString("T") + "独立线程任务开始,线程" + Thread.CurrentThread.ManagedThreadId);
ThreadParm arg = new ThreadParm();
Thread th = new Thread(arg.WorkThread);
th.Start();
//th.Join();
var resual = arg.resual;
listBox1.Items.Add(resual);
listBox1.Items.Add(DateTime.Now.ToString("T") + "独立线程任务结束,线程" + Thread.CurrentThread.ManagedThreadId);
}
运行结果如下

以上是开了一个线程运行耗时函数,用引用类型(类的实例)来接收线程返回值,主线程没有被阻塞,UI也没有假死,但结果不是我们想要的,
还没等耗时函数返回,就直接输出了结果,即我们没有拿到耗时函数的处理的结果,输出结果只是初始化的值
resual = "耗时函数未执行完";
为了得到其结果,可以用子线程阻塞主线程,等子线程运行完再继续,如下:
th.Join();
这样就能获得到耗时函数的结果,正确输出,但是在主线程挂起的时候,UI还是在假死,因此没有起到优化的作用。
5.可以把输出的结果在子线程(耗时函数)里输出,那样就主线程就不必输出等其结果了,既能输出正确的结果,又不会导致UI假死:
/// <summary>
/// 耗时工作
/// </summary>
/// <returns></returns>
private void Work()
{
Thread.Sleep(1000);
Thread.Sleep(2000);
listBox1.Items.Add(("T") + "进入耗时函数里, 线程ID:" + Thread.CurrentThread.ManagedThreadId); //步骤7:子线程运行,不阻塞主线程
}
如上修改耗时函数(其他地方修改我就省略了)再运行,会报如下错误:

于是你会说,控件跨线程访问,这个我熟呀!不就用在初始化时添加下面这句代码吗:
Control.CheckForIllegalCrossThreadCalls = false;
又或者用委托来完成。
确实可以达到目的,但是这样不够优雅,而且有时候非要等子线程走完拿到返回结果再运行下一步,所以就有了异步等待
6.异步实现方式:
/// <summary>
/// 异步任务
/// </summary>
/// <returns></returns>
private async Task TaskAsync()
{
listBox1.Items.Add(DateTime.Now.ToString("T") + "异步任务开始,线程ID:" + Thread.CurrentThread.ManagedThreadId); //步骤3:在主线程运行,阻塞主线程
var resual = await WorkAsync(); //步骤4:在主线程运行,阻塞主线程 //以下步骤都在等待WorkAsync函数返回才执行,但在等待的过程不占用主线程,所以等待的时候不会阻塞主线程
string str = DateTime.Now.ToString("T") + resual + "当前线程:" + Thread.CurrentThread.ManagedThreadId;
listBox1.Items.Add(str);//步骤10:在主线程运行,阻塞主线程
listBox1.Items.Add(DateTime.Now.ToString("T") + "异步任务结束,线程ID:" + Thread.CurrentThread.ManagedThreadId);//步骤11:在主线程运行,阻塞主线程
} /// <summary>
/// 异步工作函数
/// </summary>
/// <returns></returns>
private async Task<string> WorkAsync()
{
listBox1.Items.Add(DateTime.Now.ToString("T") + "进入耗时函数前,线程" + Thread.CurrentThread.ManagedThreadId); //步骤5:在主线程运行,阻塞主线程 //拉姆达表达式开异步线程
//return await Task.Run(() =>
//{
// Thread.Sleep(1000);
// //listBox1.Items.Add("计时开始:");
// Thread.Sleep(2000);
// //listBox1.Items.Add("计时结束");
// return "耗时:" + 30;
//}); //函数方式开异步现程
string str = await Task.Run(Work); //步骤6:这里开线程处理耗时工作,不阻塞主线程,主线程回到步骤3 //以下步骤都在等待Work函数返回才执行,但在等待的过程不占用主线程,所以等待的时候不会阻塞主线程
listBox1.Items.Add(DateTime.Now.ToString("T") + "出去异步函数前,线程" + Thread.CurrentThread.ManagedThreadId); //步骤9:主线程运行,阻塞主线程
return "运行时间" + str;
//return await Task.Run(Work);
} /// <summary>
/// 耗时工作
/// </summary>
/// <returns></returns>
private string Work()
{
Thread.Sleep(1000);
Thread.Sleep(2000);
//listBox1.Items.Add("耗时任务完成");
return DateTime.Now.ToString("T") + "进入耗时函数里, 线程ID:" + Thread.CurrentThread.ManagedThreadId; //步骤7:子线程运行,不阻塞主线程
} private void button2_Click(object sender, EventArgs e)
{
listBox1.Items.Add(DateTime.Now.ToString("T") + "调用异步之前,线程" + Thread.CurrentThread.ManagedThreadId); //步骤1
TaskAsync();//步骤2:调用异步函数,阻塞主线程
listBox1.Items.Add(DateTime.Now.ToString("T") + "调用异步之后,线程" + Thread.CurrentThread.ManagedThreadId);
}
运行结果如下:

以上就能满足我们的需求,即不会卡UI,也能等待,且在等待结束后回到主线程运行。
其运行逻辑是:

网上很多人说异步是开了线程来等待完成的, 从上图的时间轴来看,其并没有开启新的线程,都是同步往下执行。那为啥叫异步呢,因为执行到await时不发生阻塞,直接跳过等待去执行其他的,当await返回时,又接着执行await后面的代码,这一系列的运行都是在主调线程中完成,并没有开线程等待。所以如果耗时函数不开一个线程运行,一样会阻塞,没有完全利用异步的优势。
那么,await是在主线程等待,那其为什么没有阻塞主线程呢?我个人觉得其是利用委托的方式,后面再去揪原理吧!
其实异步编程很实用且优雅,特别结合lamda表达式完成,极其简洁,初学者可以多多尝试,不要避而远之。
原文作者:vv彭
原文连接:https://www.cnblogs.com/eve612/p/15778273.html
本文欢迎转载,转载标明出处!
C#进阶——从应用上理解异步编程的作用(async / await)的更多相关文章
- 异步编程新方式async/await
一.前言 实际上对async/await并不是很陌生,早在阮大大的ES6教程里面就接触到了,但是一直处于理解并不熟练使用的状态,于是决定重新学习并且总结一下,写了这篇博文.如果文中有错误的地方还请各位 ...
- 走进异步编程的世界--async/await项目使用实战
起因:今天要做一个定时器任务:五分钟查询一次数据库发现超时未支付的订单数据将其状态改为已经关闭(数据量大约100条的情况) 开始未使用异步: public void SelfCloseGpPayOrd ...
- ES7前端异步玩法:async/await理解 js原生API妙用(一)
ES7前端异步玩法:async/await理解 在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是“异步”的意思,a ...
- C#基础系列——异步编程初探:async和await
前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了 ...
- 【转】剖析异步编程语法糖: async和await
一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模 ...
- [C#]剖析异步编程语法糖: async和await
一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模 ...
- 【异步编程】Part1:await&async语法糖让异步编程如鱼得水
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...
- ES7前端异步玩法:async/await理解
在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是"异步"的意思,async用于声明一个函数是异步的 ...
- 进阶篇:以IL为剑,直指async/await
接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理. 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的 ...
随机推荐
- 【HarmonyOS】【Json解析】ZSON 与 HiJson 使用
HiLog配置 为了方便调试,查看,先设置好Hilog public static final HiLogLabel loglabel = new HiLogLabel(HiLog.LOG_APP,0 ...
- python实现skywalking邮件告警webhook接口
1.介绍 Skywalking可以对链路追踪到数据进行告警规则配置,例如响应时间.响应百分比等.发送警告通过调用webhook接口完成.webhook接口用户可以自定义. 2.默认告警规则 告警配置文 ...
- scanf("%c\n",&a)和scanf("%c",&a)区别
scanf("%c",&a); 当输入字符的时候,我们按下任意字符 + 回车的时候,回车没有被当作为分隔符,而是作为一个转义字符与输入的字符一起保存在缓存区.第一次scan ...
- AtCoder Beginner Contest 148 题解
目录 AtCoder Beginner Contest 148 题解 前言 A - Round One 题意 做法 程序 B - Strings with the Same Length 题意 做法 ...
- C++ 11新特性:std bind 原理简单图解(转载)
本文解释了bind 是如何工作的.为了清晰,我对图中的语法作了一些简化(例如,省略函数调用操作符的参数类型),并且简化了 bind 的实现. bind 可以用来将用户提供的需要一个参数的函数转换成不需 ...
- SQL:查询Mysql表结构
背景:有时需要做数据字典,其中最重要的就是表结构.经整理,编写SQL如下: 代码: 1 -- drop TABLE `cfg_data_dict` ; 2 CREATE TABLE `cfg_data ...
- vue+el-element中根据文件名动态创建dialog的方法
背景 在项目中使用对话框的通常做法是把对话框封装成组件,在使用的地方引入,然后添加到template,使用visible.sync控制对话框的显示/隐藏,监听confirm事件处理用户点击确定.如下: ...
- 什么是SEO配置
SEO是什么 搜索引擎优化,又称为SEO,即Search Engine Optimization,它是一种通过分析搜索引擎的排名规律,了解各种搜索引擎怎样进行搜索.怎样抓取互联网页面.怎样确定特定关键 ...
- JAVA 通过url下载图片保存到本地
//java 通过url下载图片保存到本地 public static void download(String urlString, int i) throws Exception { // 构造U ...
- JAVAWEB项目处理XSS漏洞攻击处理方案
对页面传入的参数值进行过滤,过滤方法如下 public static String xssEncode(String s) { if (s == null || s.equals("&quo ...