当前位置: 首页 > news >正文

ASP.NET MVC 中SignalR实现实时进度通信的深度解析

一、SignalR 是什么?能解决啥问题?

简单说,SignalR 就是一个帮网页实现 “实时聊天” 的工具。以前是客户端不停问服务器要数据(轮询),现在换成服务器主动给客户端发消息,比如后台处理到第 100 条数据了,马上告诉前端更新进度条。

具体解决这 3 个痛点:

  • 进度看得见:批量抓取企业信息时,实时显示 “已处理 200 条,失败 5 条” 这样的动态信息,不再让用户干等。
  • 不卡不费资源:用长连接代替频繁请求,就像开了个热线电话,有消息直接传,省流量还快。
  • 能随时喊停:用户觉得任务太慢想取消?前端点个按钮,后台立刻停止处理,不浪费资源。

二、前端怎么写?一步步跟着来

1. 先准备好 “聊天工具”

在网页里引入 SignalR 的客户端库,就像安装微信才能发消息一样:

@section Scripts{<!-- 基础库 --><script src="~/Scripts/jquery.min.js"></script><!-- SignalR客户端,用来连接服务器 --><script src="~/Scripts/jquery.signalR-2.4.3.min.js"></script><!-- 自动生成的"聊天房间"代码,后端定义的ProgressHub会在这里出现 --><script src="~/signalr/hubs"></script>
}

2. 建立连接:先和服务器搭上话

// 初始化连接对象
var hubConnection = $.connection.hub;
// 对应后端的ProgressHub,就像找到对应的聊天房间
var progressHub = $.connection.ProgressHub;// 告诉前端:当服务器发进度消息时,要做什么
progressHub.client.ReceiveProgress = function (progress) {// 更新网页上的进度显示$('#progressMessage').html(progress.Message);// 任务完成了就关掉进度弹窗if (progress.IsCompleted) layer.close(progressLayer);
};// 启动连接(这里用"长轮询"方式,兼容性好)
function startSignalR() {return new Promise((resolve, reject) => {if (hubConnection.state === $.signalR.connectionState.connected) {resolve(); // 已经连上了就直接下一步return;}// 开始连接,成功了走resolve,失败了走rejecthubConnection.start({ transport: 'longPolling' }).done(resolve).fail(reject);});
}

这里用 Promise 是为了保证 “必须先连上服务器,再开始任务”,就像打电话要等对方接通了再说话。

3. 启动任务 + 控制流程:用户点按钮,后台开始干活

function startBatchTask(taskEvent, taskName) {// 先弹出一个进度弹窗,告诉用户任务开始了progressLayer = layer.open({type: 1,title: `正在处理${taskName}`,content: '<div class="layui-text-center"><p id="progressMessage">加载中...</p></div>'});// 连接成功后,告诉后端开始任务startSignalR().then(() => {$.ajax({url: `/xxxx/xxxxx/${taskEvent}`,success: function (res) {if (res.Success) {currentTaskId = res.TaskId; // 记住任务ID,后续用来停任务console.log(`任务开始,ID是${currentTaskId}`);} else {layer.close(progressLayer);layer.msg('任务启动失败', { icon: 2 });}}});});
}// 用户关闭弹窗时,终止任务
function handleCloseOperation() {if (currentTaskId) {// 告诉服务器:把这个ID的任务停掉!progressHub.server.stopTask(currentTaskId);}// 断开连接+刷新表格disconnectAndRefresh();
}

这里就像用户点了 “开始下载”,先弹出下载进度条,下载过程中也能随时点 “取消”。

三、后端怎么写?让服务器会 “主动报信”

1. 定义 "聊天房间"Hub:允许前后端互相调用

[HubName("ProgressHub")] // 前端通过这个名字找到我
public class ProgressHub : Hub {// 用字典记录每个任务的取消令牌,方便停任务private static readonly Dictionary<string, CancellationTokenSource> TaskCancellationTokenSources = new Dictionary<string, CancellationTokenSource>();// 前端调用这个方法来停任务public async Task StopTask(string taskId) {lock (TaskCancellationTokenSources) { // 保证线程安全,别让多个任务抢资源if (TaskCancellationTokenSources.TryGetValue(taskId, out var cts)) {cts.Cancel(); // 取消正在进行的任务TaskCancellationTokenSources.Remove(taskId); // 从字典里删掉,避免内存泄漏}}// 告诉所有客户端(这里其实只需要告诉发起任务的客户端)任务终止了await Clients.All.SendAsync("ReceiveProgress", new { TaskId = taskId, Message = "任务已终止" });}
}

Hub 就像一个中转站,前端说 “停任务”,Hub 收到后通知后台取消,同时能给前端发消息。

2. 处理任务 + 发进度:后台干活时不忘报信

public JsonResult GetAllEnterpriseBaseInfo() {var taskId = Guid.NewGuid().ToString("N"); // 生成唯一任务ID// 用Task.Run开一个后台线程处理,别阻塞主线程Task.Run(() => ProcessEnterpriseDataAsync(taskId, _enterpriseService.GetFactoryList(), _baseInfoUrl, "企业基本信息"));return Json(new { Success = true, TaskId = taskId }); // 告诉前端任务启动成功,给个ID
}private async Task ProcessEnterpriseDataAsync(string taskId, List<FactoryModel> factoryList, string apiUrl, string taskType) {var totalCount = factoryList.Count;var processedCount = 0;var failedCount = 0;try {for (int i = 0; i < totalCount; i += 200) { // 分批处理,一次处理200条var batch = factoryList.Skip(i).Take(200).ToList();// 处理每一条数据...中间省略具体逻辑...processedCount += batch.Count; // 记录处理了多少条// 每处理10条或者处理完了,就给前端发一次进度if (processedCount % 10 == 0 || processedCount == totalCount) {SendProgressUpdateAsync(taskId, totalCount, processedCount, failedCount, taskType, processedCount >= totalCount);}}} catch (Exception ex) {// 出错了也告诉前端SendProgressUpdateAsync(taskId, totalCount, processedCount, totalCount, taskType, true, "任务出错啦:" + ex.Message);}
}private void SendProgressUpdateAsync(string taskId, int total, int done, int failed, string taskName, bool isDone, string msg = null) {var hubContext = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>(); // 获取Hub实例var message = msg ?? (isDone ? $"完成啦!总共{total}条,失败{failed}条" : $"处理中...已完成{done}/{total}条");// 给所有客户端发消息,这里其实应该只发给发起任务的客户端,不过示例简化了hubContext.Clients.All.ReceiveProgress(new ProgressUpdate {TaskId = taskId,Message = message,IsCompleted = isDone});
}

核心就是:后台每处理一批数据,就调用SendProgressUpdateAsync告诉前端当前进度,就像快递员每到一个站点就更新物流信息。

四、SignalR 背后的原理:到底怎么做到实时的?

1. 连接方式:就像不同的通信工具

SignalR 会根据浏览器能力自动选择最合适的连接方式:

  • WebSocket:现代浏览器首选,像开了个实时聊天窗口,双向通信最快。
  • 长轮询:浏览器不支持 WebSocket 时用,就像客户端问 “有新消息吗?”,服务器没消息就等着,有消息马上回,比频繁轮询省流量。
  • 还有一些兼容旧浏览器的方式,比如 IFrame,不过现在用得少了。

2. 双向通信:前后端能互相调用

  • 前端可以调用后端 Hub 里的方法,比如progressHub.server.stopTask(),就像给服务器发了一条指令:“把这个任务停了”。
  • 后端可以调用前端的方法,比如hubContext.Clients.All.ReceiveProgress(),相当于服务器给前端发消息:“进度更新啦,快显示给用户看”。

3. 异步处理:后台干活不阻塞

Task.Runasync/await让耗时操作(比如网络请求、数据库插入)在后台线程执行,就像你一边下载文件一边浏览网页,互不影响,服务器能同时处理更多任务。

五、为啥非得用 SignalR?对比一下就知道

方案优点缺点适合场景
轮询简单,兼容性好巨费流量,巨慢数据更新极慢场景
WebSocket最快,实时性最强自己处理兼容性超麻烦高实时性需求
SignalR自动适配连接方式,代码少多引入一个库(很小)90% 的实时场景

简单说:SignalR 帮你把复杂的底层通信搞定了,你只需要关注 “什么时候该发进度” 和 “收到进度怎么显示”,开发效率飙升!

六、效果图长啥样?交互流程走一遍

1. 大概长这样(示意图):

在这里插入图片描述
(实际图会有 “开始任务” 按钮、进度文字、取消按钮,任务完成后自动关闭)

2. 用户操作步骤:

  • 点击 “一键获取企业信息” → 弹出进度弹窗,显示 “正在连接服务器”。
  • 连接成功后,后台开始处理,弹窗显示 “已处理 50 条 / 1000 条”。
  • 处理过程中用户可点击弹窗关闭按钮 → 后台任务终止,弹窗显示 “任务已取消”。
  • 任务正常完成后,弹窗自动关闭,数据表格刷新显示最新结果。

七、新手注意!这些坑别踩

  • 连接失败要重试:网络不好时可能连接不上,前端加个重试逻辑,比如 3 秒后再连一次。
  • 别发太多消息:后台每处理 10 条发一次进度就够了,别每条都发,不然前端会卡。
  • 任务 ID 要保密:别把任务 ID 随便暴露,防止有人恶意终止别人的任务,后端停任务前检查用户权限。
  • 服务端客户端事件名称要一致:服务端发送的方法要和页面注册的名称一致,要不然发送不了
  • 旧浏览器兼容:如果用户用 IE8 之类的古董,可能需要启用 Forever Frame 模式,但现在基本没人用了,建议引导用户升级浏览器。
  • -先连接后通信:千万要记住先要通了话,才能说话。要不然即使后端发送了消息,等前端连接上也接收不到,消息就会报废掉

总结:SignalR 就是实时通信的 “懒人工具”

用 SignalR,就像点外卖用美团,不用自己找骑手,平台都帮你搞定。前端简单几行代码就能接收进度,后端调用 Hub 发消息就行,开发效率翻倍,用户体验还好。下次遇到需要实时更新的场景,直接上 SignalR 准没错!

相关文章:

  • 网络资源模板--基于Android studio 通讯录App
  • JSONPath常用表达式
  • WPF学习(二)
  • 蓝牙版本演进史:从 1.0 到 5.4 的技术突破 —— 面试高频考点与历年真题详解
  • LeetCode--34.在排序数组中查找元素的第一个和最后一个位置
  • css3 背景色渐变
  • 【java中使用stream处理list数据提取其中的某个字段,并由List<String>转为List<Long>】
  • DNS服务管理企业级实战模拟
  • Flask视频和图片上传
  • win11装vm虚拟机创建Linux常见问题!
  • 线上线下融合驱动:开源链动2+1模式与AI智能名片赋能高价值社群生态的机制研究
  • 常见的Dolphin Scheduler报错
  • Docker Compose部署Spring Cloud 微服务系统
  • 腾讯云搭建web服务器的方法
  • extern关键字:C/C++跨文件编程利器
  • FPGA基础 -- Verilog行为级建模之时序控制
  • 回溯----5.括号生成
  • 如何通过 5 种方式向 Android 手机添加音乐
  • ubuntu下python版本升级导致pyqt不能正常运行解决
  • MSYS2 环境下 Python 开发配置(结合 PyCharm)使用笔记
  • 网站建设陆金手指下拉壹玖/seo公司网站推广
  • 平面设计接单赚钱吗/游戏优化软件
  • 香港免费永久网站/创建网站需要多少资金
  • 顺德网站建设多少钱/电脑优化大师官方免费下载
  • 萍乡网站建设哪家公司好/seo排名优化公司价格
  • 工程建设标准最新查询网站/搜狗网站收录入口