欢迎来到学习摆脱又加深内卷篇

下面是学习异步编程的应用

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)的更多相关文章

  1. 异步编程新方式async/await

    一.前言 实际上对async/await并不是很陌生,早在阮大大的ES6教程里面就接触到了,但是一直处于理解并不熟练使用的状态,于是决定重新学习并且总结一下,写了这篇博文.如果文中有错误的地方还请各位 ...

  2. 走进异步编程的世界--async/await项目使用实战

    起因:今天要做一个定时器任务:五分钟查询一次数据库发现超时未支付的订单数据将其状态改为已经关闭(数据量大约100条的情况) 开始未使用异步: public void SelfCloseGpPayOrd ...

  3. ES7前端异步玩法:async/await理解 js原生API妙用(一)

    ES7前端异步玩法:async/await理解   在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是“异步”的意思,a ...

  4. C#基础系列——异步编程初探:async和await

    前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了 ...

  5. 【转】剖析异步编程语法糖: async和await

    一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模 ...

  6. [C#]剖析异步编程语法糖: async和await

    一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模 ...

  7. 【异步编程】Part1:await&async语法糖让异步编程如鱼得水

    前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...

  8. ES7前端异步玩法:async/await理解

    在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是"异步"的意思,async用于声明一个函数是异步的 ...

  9. 进阶篇:以IL为剑,直指async/await

    接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理. 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的 ...

随机推荐

  1. 【Spark】【RDD】从本地文件系统创建RDD

    练习作业 完成任务从文件创建三个RDD(math bigdata student) cd ~ touch math touch bigdata touch student pwd 启动Spark-sh ...

  2. C#内建接口:IEnumerable

    这节讲一下接口IEnumerable. 01 什么是Enumerable 在一些返回集合数据的接口中,我们经常能看到IEnumerable接口的身影.那什么是Enumerable呢?首先它跟C#中的e ...

  3. WebDriver驱动下载地址

    chrome的webdriver: http://chromedriver.storage.googleapis.com/index.html Firefox驱动下载地址为:https://githu ...

  4. centos源码安装ruby

    目录 一.简介 二.程序部署 一.简介 Ruby,一种简单快捷的面向对象(面向对象程序设计)脚本语言.rvm是ruby的管理器,可以切换ruby版本,下载ruby. 二.程序部署 1.下载ruby w ...

  5. 【紧急】继续折腾,Log4j再发2.1.6,强烈建议升级

    背景 继前天正式发布的2.15.0之后,Apache log4j 2 团队宣布 Log4j 2.16.0 发布! 由于SLF4J适配兼容性的中断,Log4j 现在发布两个版本的SLF4J to Log ...

  6. DIgSILENT PowerFactory 15.1.7 破解过程

    将dll文件复制到安装路径下:

  7. Excel数据导出功能

    HTML代码: <a id="aExportData" hidden><span>Export</span></a> <div ...

  8. 【手把手教程】uniapp + vue 从0搭建仿微信App聊天应用:腾讯云TXIM即时通讯的最佳实践

    基于uniapp + vue 实现仿微信App聊天应用实践,实现以下功能 1: 用户登陆 2: 聊天会话管理 3: 文本/图片/视频/定位消息收发 4: 贴图表情消息收发 5: 一对一语音视频在线通话 ...

  9. SPringBoot 配置类继承WebMvcConfigurationSupport和实现WebMvcConfigurer的使用

    个人习惯使用  实现的方式 public class WebMvcConfiguration implements WebMvcConfigurer {

  10. vue-子组件创建/注册/使用流程

    流程分为三步 非单文件组件:(实际不用,因为很麻烦,框架都是多文件组件) 局部注册 1.创建一个组件 const school = Vue.extend({ // 传入配置对象 // 子组件配置对象不 ...