热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨

本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。




39a18ef4d54bc3b701e59cfa666f1382.gif

e00d39b65247451a91782791f2fdbcd7.png

    

一、异步编程的运行机制


    我们学习Javascript语言的时候就知道它的执行环境是”单线程“的。


    所谓”单线程“,就是指一次只能处理一个任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。  


    所以为了解决“单线程”引发的出来的问题,就有了前端异步编程的这个解决方案,下面我们就来探讨一下浏览器的异步编程是一个怎么的运行机制。


1.1异步任务


    前端中常见的异步任务有以下:


•    回调函数


•     事件绑定


•     定时器(settimeout ,setinterval)


•     ajax


•     Promise


    是异步的吗?我们来验证下:


new Promise((resolve,reject)=>{ console.log(1); resolve();}).then(()=>{ console.log(2);})console.log(3);

    我们看下控制台的输出顺序:


8e1935f5db7c1c6ce24b68fc72bc722b.png


    由上可见 new Promise 的时候传递的executor函数是立即执行的(同步),基于then、catch存放的方法是异步执行的。


•     async/await


    这是基于Generator语法糖,同样也是异步的,我们把上面的例子改成这样看看。


async function async1() {console.log(1);await 10;console.log(2);}fn();console.log(3);

我们看下控制台的输出顺序:


8e1935f5db7c1c6ce24b68fc72bc722b.png


由上可见 await 表达式后面的代码是异步执行的 。


1.2Event Loop


    那么JS是如何构建出异步编程的效果呢?那我们就来了解下事件循环机制(Event Loop )与事件队列(Event Queue)。


cd602300c9ba9aa5e9da276fd22cf0f1.png


1、开始,任务执行。


2、同步任务直接在主栈(Call Stack)中等待被执行,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。


3、当 Call Stack中没有任务了,就从Event Queue中拿出一个任务放入Call Stack执行。


而 Event Loop 指的就是这一整个圈圈:它不停检查 Call Stack 中是否有任务(也叫栈帧)需要执行,如果没有,就检查 Event Queue,从中弹出一个任务,放入主栈中,如此往复循环。


我们来看个例子: 


setTimeout(()=>{ console.log(1);},50)setTimeout(()=>{ console.log(2);},0)console.time('for takes time:');for(let i=0;i<1000000;i++){}console.timeEnd('for takes time:');setTimeout(()=>{ console.log(3);},20)console.log(4); 

我们先看下执行结果再来分析。


e7634c6dcce2a9edf497d34b77643a20.png


代码分析如下:


a8a0410b904794dc24c716ae1f248ea1.png


    这里主要容易疑惑点的点是T1先执行还是T3先执行,这里要看for 循环主要消耗的时间,for 循环用了5ms(假设是个整数),这时候队列中T1需要等待的时间还有45ms,所以执行完T2后,等待到了20ms 就会执行T3,最后才执行T1。如果把for循环耗的时间是100ms,那么执行顺序就是T2=>T1=>T3,遵守时间到先执行,先进队列先执行的原则。


    我们来看个稍微复杂点的例子:


async function async1(){ console.log('async1 start'); await async2(); console.log('async1 end');}async function async2(){ console.log('async2');}console.log('script start');setTimeout(function(){ console.log('setTimeout');})async1();new Promise(function(resolve){ console.log('promise1'); resolve();}).then(function(){ console.log('promise2');})console.log('script end');

    学习上面的Event Loop ,同步任务先执行,再遵守先进先出的原则从Event Queue中取出异步任务到主栈中执行,我自信的以为输出顺序为:


'script start' 'async1 start' 'async2' 'promise1'    'script end'  'setTimeout'  'async1 end'   'promise2'   

然后迫不及待的去控制台执行下:


d7af7c5ecc57e7b4c63b190ff171de1e.png


2093324ecd1427204aaa3d2efbd4d04f.gif


为什么顺序执行是这样子的?于是继续查资料。


1.3宏任务与微任务


Javacript中事件执行主要分为两种任务类型:宏任务(macro task)与微任务队列(micro task)。


•     宏任务包含有 :


script(整体代码)setTimeoutsetIntervalI/OUI交互事件postMessageMessageChannelsetImmediate(Node.js 环境)

     微任务包含有 :


Promise.then/catch/finaly, async/await(本质是对Promise 的一些封装)Object.observeMutaionObserverprocess.nextTick(Node.js 环境)

1.4宏任务与微任务运行机制


1、执行一个宏任务(栈中没有就从Event Queue中取出)


2、遇到异步宏任务时它加入到Event Queue宏任务队列中;遇到异步微任务时把它加入Event Queue微任务队列中。


3、宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)


4、当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染


5、渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)


如下图:


0e953b93acb3b665a5449db3d4cf3f59.png


所以上面例子(代码5)的执行分析如下图所示:


31d1a215300036c2fb3fb980deaba8ff.png


d6f1bbfbb33178cd606d495a79d6d47c.gif


1.5总结以上的内容


•     Javascript是一门单线程语言


•     Javascript事件循环就是一个Event Loop 过程


•     Event Queue 中的又分为宏任务队列与微任务队列


•     Javscript代码的执行顺序:同步任务=>异步微任务=>异步宏任务


    

二、异步编程使用场景


    上面内容我们探讨了浏览器异步编程是的运行机制。我们一般会在哪些场景使用异步编程呢?下面我们看个例子:


a9938581924ffc194d569c33887862ec.png


    现在有个业务需求就是要先请求接口1拿到“系统”的下拉数据后,并且要给表单赋值为下拉数据的第一个,拿到“系统”的value值后再请求接口2取到“表单分类”的下拉数据,并且要给表单赋值,拿到“表单分类“的值后再请求接口3,取表单的下拉数据。
我们传统的做法一般会用回调函数:


//获取系统_self.ajaxFn({ url:url1, success:(res)=>{ //获取表单分类 _self.ajaxFn({ url:url2, success:(res)=>{ //获取表单 _self.ajaxFn({ url:url3, success:(res)=>{ //处理业务 } }) } }) }})

2.1回调地狱


    回调函数是异步编程的一个传统解决方案,但是如果业务比较复杂,需要层层嵌套,就会引起回调地狱。


66e474f4f674bff8156f7bcbfe3251f4.png


这样子的代码嵌套复杂,不易于维护 ,着实让开发者崩溃 。


431c34dc7e2d2dbcda1a5c1e06647c28.png


三、Promise原理及实现


3.1Promise原理及实现


    Promise是异步编程的一个解决方案,相比回调函数,使用Promise更为合理和强大,避免了回调函数之间的多嵌套,也使得代码结构更为清晰,便于维护。上面的例子我们用Promise来实现下。


new Promise((resolve,reject)=>{ //获取系统 _self.ajaxFn({ url:url1, success:(res)=>{ resolve(res); } })}).then( value=>{ return new Promise((resolve,reject)=>{ //获取表单分类 _self.ajaxFn({ url:url2, success:(res)=>{ resolve(res); } }) }) }).then( value=>{ //获取表单 _self.ajaxFn({ url:url1, success:(res)=>{ //处理逻辑 } }) })

接下来我们就要分析Promise是原理并且要自己实现Promise。


3.2 promise自定义实现的关键点


1、如何改变promise的状态?


1.1 resolve(value);状态从pending 转为了 resolved


1.2 rejecte(reason);状态从pending 转为了 rejected


1.3 抛出异常(throw);当前的pending 就变为了 rejected


2、Promise then/catch 存放的回调函数是异步微任务的 。


3、Promise.then()返回的新的Promise的结果状态由什么决定的呢?


3.1 简单描述:由then指定的回调函数执行的结果决定


3.2 详细描述:


3.2.1 如果抛出异常,新Promise变为rejected, reason 为抛出的异常;


3.2.2 如果返回的是Promise的任意值,新promise变为resolved,value 为返回的值


3.2.3 如果返回的是另一个Promise,此Promise的结果成为新Promise的结果


4、Promise.then()/catch()可链式调用,所以then(),catch方法必须返回一个的Promise对象。


3.3 自定义Promise


1、定义整体结构


(function(){ /* 构造函数 excutor:执行器(同步执行) */ function Promise(excutor){} /* Promise 原型对象的then() 指定成功和失败的回调函数 因为可链式调用,所以要返回一个promise对象 */ Promise.prototype.then=function(onResolved,onRejected){ } /* Promise 原型对象的catch() 指定失败的回调函数 返回一个promise对象 */ Promise.prototype.catch=function(onRejected){ } /* 只实现Promise关键 的构造方法以及catch/then方法,其它的方法大家可自行发挥啦 */ window.Promise=Promise;})(window)

2、Promise中回调的函数的异步执行


    第一部分内容我们就知道Promise.then/catch中存放是任务是方式是异步执行,且是微任务。异步执行我们很容易就能想到setTimeout能实现,但是setTimeout是宏任务,所以不符合,于是我就找到了Vue中的nextTick的方法来模拟一下,它主要是用HTML5新的API MutationObserver(不熟悉的可自行了解下)来实现的,第一部分内容我们有说到MutationObserver是微任务。


const nextTick = (function () { var callbacks = []; var pending = false; var timerFunc; function nextTickHandler () { pending = false; var copies = callbacks.slice(0); callbacks = []; for (var i = 0; i

3、Promise 的构造函数的实现


/* 构造函数 excutor:执行器*/function Promise(excutor){ const _self=this; _self.status='pending';//promise对象指定的status属性,初始值为pending; _self.data=undefined;//promise对象指定一个用于存储结果数据的属性; _self.callbacks=[];//失败或成功的回调函数数组,每个元素的结构:{onResolved(){},onRejected(){}} function resolve(value){ if(_self.status!='pending'){ return; } _self.status='resolved'; //将状态改为resolved _self.data=value; // 保存value //如果有待将执行的回调函数,就立即异步执行回调函数 onResolved nextTick(()=>{ _self.callbacks.forEach((callbacksObj)=>{ callbacksObj.onResolved(value); }) }) } function reject(reason){ if(_self.status!='pending'){ return; } _self.status='rejected'; //将状态改为rejected _self.data=reason; // 保存value //如果有待将执行的回调函数,就立即异步执行回调函数 onRejected nextTick(()=>{ _self.callbacks.forEach((callbacksObj)=>{ callbacksObj.onRejected(reason); }) }) } //立即执行执行器 try{ excutor(resolve,reject); } catch(error){ reject(error); } }

4、Promise.prototype.then()方法的实现


/* Promise 原型对象的then()方法 指定成功和失败的回调函数 因为可链式调用,所以必须要要返回一个promise对象 返回的promise 的结果是由onResolved/onRejected执行的结果决定的*/Promise.prototype.then=function(onResolved,onRejected){ var _self=this; //指定回调函数的默认值,如果参数不是函数就指定个默认方法 OnResolved=typeof OnResolved==='function'? onResolved :value => value; OnRejected=typeof OnRejected==='function'? onRejected :reason => {throw reason}; return new Promise((resolve,reject)=>{ /* 执行指定callback(onResolved/onRejected)的回调函数 根据执行的结果改变return的pormise的状态 */ function handle(callback){ try{ const result=callback(_self.data); if(result instanceof Promise){ //返回的是promise,返回promise的结果就是这个结果 result.then(resolve,reject) }else{//返回的不是promise,返回的promise为成功,value就是返回值 resolve(result) } }catch(error){ //抛出异常,返回promise的结果为失败,reason为异常 reject(error) } } if(_self.status==='resolved'){ //当前promise的状态是resolved //异步执行成功的回调 nextTick(()=>{ handle(onResolved) }) }else if(_self.status==='rejected'){//当前promise的状态是rejected //异步执行失败的回调 nextTick(()=>{ handle(onRejected) }) }else{ //当前promise 的状态是pending //将成功和失败的回调函数保存到callback中去存起来 _self.callbacks.push({ onResolved(){ handle(onResolved); }, onRejected(){ handle(onRejected); } }) } })

5、Promise.prototype.catch()方法的实现


/* Promise 原型对象的catch() 指定失败的回调函数 返回一个promise对象*/Promise.prototype.catch=function(onRejected){ return this.then(null,onRejected)}

    大功告成,其实主要是把then 方法实现,其它的都好实现了,其它的方法大家试下吧。


39a18ef4d54bc3b701e59cfa666f1382.gif

四、最后


    这篇文章主要跟大家探讨了浏览器异步编程的运行机制还有Promise的实现原理。


    前端之路漫漫其修远兮,吾将上下而求索,与君共勉!06667120783b80f618402b95fb0c57fe.png


—— E N D ——


排版:chuanrui


15e10f3fdecfa3f6cd98e5127af470a1.gif


4a984a305d70607b901b8637ee79d85f.png




推荐阅读
  • 本文探讨了如何利用 jQuery 的 JSONP 技术实现跨域调用外部 Web 服务。通过详细解析 JSONP 的工作原理及其在 jQuery 中的应用,本文提供了实用的代码示例和最佳实践,帮助开发者解决跨域请求中的常见问题。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 本文详细介绍了 InfluxDB、collectd 和 Grafana 的安装与配置流程。首先,按照启动顺序依次安装并配置 InfluxDB、collectd 和 Grafana。InfluxDB 作为时序数据库,用于存储时间序列数据;collectd 负责数据的采集与传输;Grafana 则用于数据的可视化展示。文中提供了 collectd 的官方文档链接,便于用户参考和进一步了解其配置选项。通过本指南,读者可以轻松搭建一个高效的数据监控系统。 ... [详细]
  • 本文探讨了使用JavaScript在不同页面间传递参数的技术方法。具体而言,从a.html页面跳转至b.html时,如何携带参数并使b.html替代当前页面显示,而非新开窗口。文中详细介绍了实现这一功能的代码及注释,帮助开发者更好地理解和应用该技术。 ... [详细]
  • 本文详细探讨了 jQuery 中 `ajaxSubmit` 方法的使用技巧及其应用场景。首先,介绍了如何正确引入必要的脚本文件,如 `jquery.form.js` 和 `jquery-1.8.0.min.js`。接着,通过具体示例展示了如何利用 `ajaxSubmit` 方法实现表单的异步提交,包括数据的发送、接收和处理。此外,还讨论了该方法在不同场景下的应用,如文件上传、表单验证和动态更新页面内容等,提供了丰富的代码示例和最佳实践建议。 ... [详细]
  • 设计实战 | 10个Kotlin项目深度解析:首页模块开发详解
    设计实战 | 10个Kotlin项目深度解析:首页模块开发详解 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 在CentOS 7环境中安装配置Redis及使用Redis Desktop Manager连接时的注意事项与技巧
    在 CentOS 7 环境中安装和配置 Redis 时,需要注意一些关键步骤和最佳实践。本文详细介绍了从安装 Redis 到配置其基本参数的全过程,并提供了使用 Redis Desktop Manager 连接 Redis 服务器的技巧和注意事项。此外,还探讨了如何优化性能和确保数据安全,帮助用户在生产环境中高效地管理和使用 Redis。 ... [详细]
  • 在PHP中如何正确调用JavaScript变量及定义PHP变量的方法详解 ... [详细]
  • Keepalived 提供了多种强大且灵活的后端健康检查机制,包括 HTTP_GET、SSL_GET、TCP_CHECK、SMTP_CHECK 和 MISC_CHECK 等多种检测方法。这些健康检查功能确保了高可用性环境中的服务稳定性和可靠性。通过合理配置这些检查方式,可以有效监测后端服务器的状态,及时发现并处理故障,从而提高系统的整体性能和可用性。 ... [详细]
  • QT框架中事件循环机制及事件分发类详解
    在QT框架中,QCoreApplication类作为事件循环的核心组件,为应用程序提供了基础的事件处理机制。该类继承自QObject,负责管理和调度各种事件,确保程序能够响应用户操作和其他系统事件。通过事件循环,QCoreApplication实现了高效的事件分发和处理,使得应用程序能够保持流畅的运行状态。此外,QCoreApplication还提供了多种方法和信号槽机制,方便开发者进行事件的定制和扩展。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
  • 本文详细介绍了如何安全地手动卸载Exchange Server 2003,以确保系统的稳定性和数据的完整性。根据微软官方支持文档(https://support.microsoft.com/kb833396/zh-cn),在进行卸载操作前,需要特别注意备份重要数据,并遵循一系列严格的步骤,以避免对现有网络环境造成不利影响。此外,文章还提供了详细的故障排除指南,帮助管理员在遇到问题时能够迅速解决,确保整个卸载过程顺利进行。 ... [详细]
author-avatar
877762833_166d01
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有