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使用可以非常优雅的 ...
随机推荐
- 【JavaScript】创建全0的Array
1.创建一个长度为m的全0数组 var arr = new Array(m).fill(0); 2.创建一个m行n列的全0数组 var arr = new Array(m).fill(new Arra ...
- 制作一个有趣的涂鸦物联网小项目(涂鸦模组SDK开发 CBU BK7231N WiFi+蓝牙模组 HSV彩色控制)
实现的功能: l APP控制月球灯 l 本地月球灯控制 l APP控制"大白"颜色,实现各种颜色变身 l 门状态传感器状态APP显示 l 网络状态指示灯,连接服务器长亮, ...
- oracle11gR2、client及plsql完整安装与配置
本文主要介绍Oracle11g,client及PLSQL的安装过程 一,oracle安装 安装环境:虚拟机win7 64 1.点击目录中 setup.exe文件 2.配置安全更新中,取消通过my or ...
- xfs文件系统修复方法https://blog.csdn.net/yuanfang_way/article/details/78700089
首先尝试mount和umount文件系统,以便重放日志,修复文件系统,如果不行,再进行如下操作. 1.检查文件系统:先确保umount xfs_check /dev/sdd(盘符); echo $? ...
- Uni-app原生插件入门使用教程-[1]从Uni-app插件市场试用插件
[1]从Uniapp插件市场试用插件 当HBuilderX中提供的能力无法满足App功能需求,需要通过使用Andorid/iOS原生开发实现时,可使用App离线SDK开发原生插件来扩展原生能力. 如使 ...
- 总结Vue第二天:自定义子组件、父子组件通信、插槽
总结Vue第二天:自定义子组件.父子组件通信.插槽 一.组件: 组件目录 1.注册组件(全局组件.局部组件和小demo) 2.组件数据存放 3.父子组件通信(父级向子级传递数据.子级向父级传递数据) ...
- 【LeetCode】325. Maximum Size Subarray Sum Equals k 解题报告 (C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 prefix Sum 日期 题目地址:https:// ...
- 【LeetCode】263. Ugly Number 解题报告(Java & Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 除去2,3,5因子 日期 [LeetCode] 题目 ...
- 【LeetCode】210. Course Schedule II 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 拓扑排序,BFS 拓扑排序,DFS 参考资料 日期 ...
- 【LeetCode】423. Reconstruct Original Digits from English 解题报告(Python)
[LeetCode]423. Reconstruct Original Digits from English 解题报告(Python) 标签: LeetCode 题目地址:https://leetc ...