【Javascript】解决Ajax轮询造成的线程阻塞问题(过渡方案)
一、背景
开发Web平台时,经常会需要定时向服务器轮询获取数据状态,并且通常不仅只开一个轮询,而是根据业务需要会产生数个轮询。这种情况下,性能低下的Ajax长轮询已经不能满足需求,频繁的访问还会造成线程阻塞。最优的解决方案当然是用Websocket,采用服务器推送的方式来减少频繁开关连接造成的开销。但是Websocket对于我来说还只是个新事物,在未完成论证的情况下不能直接开发完就上,因此只好采用过渡方案,使用队列的方式,暂时优化多AJax长轮询的情况下造成的线程阻塞问题。
我所用的Web平台框架是国产开源的DWZ框架。该框架不使用经典的iframe模式,所有的视图、数据访问都是通过Ajax获取后在前台进行加载渲染,页面迁移跳转极少,因此本质上来说基于DWZ框架的网页都是Single Page页面。在这种情况下,除了长轮询外,还会根据用户的操作产生其它Ajax链接。这就要求在优化的同时,还要保证用户操作的优先度。毕竟长轮询只是后台默认执行的操作,对用户的体验影响不大;但用户的操作因为长轮询造成延迟的话,用户体验就十分糟糕。
此外,我还发现处理这些Ajax轮询所用的Controller是MVC默认的,然而这些Controller不支持异步处理请求操作,在多个请求访问时,新请求必须等待旧请求完成后才能继续下去。
综上所述,优化Ajax轮询造成的线程阻塞问题的过渡方案中,有以下两点要求:
1.使用Ajax队列的方式,不推倒现有的技术方案,在原有的基础上快速修改。
2.在Ajax队列优化过程中,必须保证用户操作的优先度,保证用户操作的及时响应。
3.替换原有只支持同步Action的Controller,使用可支持异常Action的Controller。
二、前台代码解析
总体思路是:
1.重写jquery既有的ajax方法,将所有调用该方法的ajax全部注册到自定义的ajax程序池中。
2.自定义ajax程序池分全局和非全局两类,长轮询发起的ajax为非全局,用户发起的ajax为全局。
3.排队执行两个程序池中的请求,一个请求完成后才继续执行下一个,而非异步将所有ajax同时发起请求。
4.全局ajax的优先度高,如果当前正在执行非全局ajax且有未发起的全局ajax,则停止正在执行的非全局ajax,优先发送全局ajax。
5.非全局ajax只有在全局ajax全部完毕的情况下才会发送请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | // 所有ajax请求都注册到DNE.LoadingAjax的ajax程序池中,排队发起请求,ajax结束时删除. DNE.LoadingAjax = { jqAjax: $.ajax, requests: {}, // ajax对象集合 globalAjaxPool: [], // 全局ajax程序池 unglobalAjaxPool: [], // 非全局ajax程序池 interval: null , // ajax循环定时器 runningType: null , // 正在运行的Ajax类型 1:全局 2:非全局 runningId: null , // 正在运行的AjaxId // 注册Ajax到程序池中 PushAjaxPool: function (request, options) { var urlComplete = request.complete; var requests = this .requests; var id = (request.tabId) ? request.tabId : request.url; // 请求结束时,删除ajax对象 request.complete = this .deleteAjax(urlComplete, id); // 将请求放到ajax程序池中 var requestObj = { id: id, request: request, options: options }; // 如果是获取json数据的请求,则放入程序池中,如果是获取Js或图片等资源的请求,则直接执行 if (requestObj.request.dataType == "json" ) { if (request.global) { // 如果是全局ajax this .globalAjaxPool.push(requestObj); } else { // 如果不是全局ajax this .unglobalAjaxPool.push(requestObj); } } else { var loadingAjax = DNE.LoadingAjax; loadingAjax.runAjax(requestObj); } if (! this .interval) { this .interval = window.setInterval( function () { var loadingAjax = DNE.LoadingAjax; // 如果当前有全局Ajax未运行,则停止正在运行的非全局Ajax if (loadingAjax.runningType != 1 && loadingAjax.globalAjaxPool.length > 0) { if (loadingAjax.runningType == 2 && loadingAjax.runningId) { loadingAjax.ajaxAbort(id); } // 运行最开头的全局Ajax var reqObj = loadingAjax.globalAjaxPool.shift(); loadingAjax.runAjax(reqObj); } else { // 如果当前没有正在执行的Ajax,并且非全局Ajax程序池中有对象 if (loadingAjax.runningType == null && loadingAjax.unglobalAjaxPool.length > 0) { // 运行最开头的非全局Ajax var reqObj = loadingAjax.unglobalAjaxPool.shift(); loadingAjax.runAjax(reqObj); } } }, 100); } }, // 删除Ajax deleteAjax: function (urlComplete, id) { if (urlComplete && typeof (urlComplete) == "function" ) { urlComplete(); } var loadingAjax = DNE.LoadingAjax; if (loadingAjax.requests[id]) { delete loadingAjax.requests[id]; } // 如果程序池中已无请求,则清空ajax循环定时器 if (loadingAjax.globalAjaxPool.length <= 0 && loadingAjax.unglobalAjaxPool.length <= 0) { loadingAjax.interval = null ; } // 如果当前请求结束,则重置正在运行的Ajax类型及AjaxId loadingAjax.runningType = null ; loadingAjax.runningId = null ; }, // 执行Ajax runAjax: function (reqObj) { var jqXHR = this .jqAjax(reqObj.request, reqObj.options); this .requests[reqObj.id] = jqXHR; }, // 停止Ajax ajaxAbort: function (id) { var jqXHR = this .requests[id]; if (jqXHR) { jqXHR.abort(); delete this .requests[id]; } } }; $( function () { $.extend({ ajax: function (url, options) { // 所有ajax都注册到ajax程序池中 DNE.LoadingAjax.PushAjaxPool(url, options); } }); }); |
三、后台代码解析(待续)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?