如何为特定用户实现ASP.NET Core后台任务及专属UI更新
实现方案:ASP.NET Core MVC/Razor Pages 专属用户后台任务 + SignalR 实时更新
核心思路
用 SignalR 实现实时推送 替代 AJAX 轮询,搭配 用户专属任务管理器 隔离不同用户的后台任务,确保更新只推送给对应用户,且用户跳转页面返回后能续接同一任务的状态。
步骤1:配置 SignalR 实时通信
SignalR 是 ASP.NET Core 官方的实时通信框架,能直接将后端更新推送到前端,比 AJAX 轮询更高效。
1.1 创建 SignalR Hub
这个 Hub 负责管理用户连接,关联用户ID和SignalR连接ID:
using Microsoft.AspNetCore.SignalR; using System.Security.Claims; public class TaskUpdateHub : Hub { private readonly UserTaskManager _taskManager; public TaskUpdateHub(UserTaskManager taskManager) { _taskManager = taskManager; } public override async Task OnConnectedAsync() { var userId = Context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (!string.IsNullOrEmpty(userId)) { await _taskManager.AddUserConnection(userId, Context.ConnectionId); } await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? exception) { var userId = Context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (!string.IsNullOrEmpty(userId)) { await _taskManager.RemoveUserConnection(userId, Context.ConnectionId); } await base.OnDisconnectedAsync(exception); } }
1.2 注册 SignalR 服务
在 Program.cs 中添加SignalR配置:
var builder = WebApplication.CreateBuilder(args); // 注册SignalR服务 builder.Services.AddSignalR(); // 注册用户任务管理器(单例,全局唯一) builder.Services.AddSingleton<UserTaskManager>(); // 注册MVC/Razor Pages服务 builder.Services.AddRazorPages(); builder.Services.AddControllers(); var app = builder.Build(); // 配置SignalR端点 app.MapHub<TaskUpdateHub>("/taskUpdateHub"); // 其他中间件配置... app.MapRazorPages(); app.MapControllers(); app.Run();
步骤2:实现用户专属任务管理器
创建一个单例服务 UserTaskManager,用来跟踪每个用户的后台任务和连接,确保任务与用户绑定:
using Microsoft.AspNetCore.SignalR; using System.Collections.Concurrent; public class UserTaskManager { private readonly IHubContext<TaskUpdateHub> _hubContext; // 存储用户ID与任务的映射 private readonly ConcurrentDictionary<string, UserBackgroundTask> _userTasks = new(); // 存储用户ID与SignalR连接ID的映射(支持多设备登录) private readonly ConcurrentDictionary<string, List<string>> _userConnections = new(); public UserTaskManager(IHubContext<TaskUpdateHub> hubContext) { _hubContext = hubContext; } // 启动用户专属任务 public async Task StartUserTask(string userId) { if (_userTasks.ContainsKey(userId)) return; var cts = new CancellationTokenSource(); var task = new UserBackgroundTask(userId, cts.Token, this); _userTasks.TryAdd(userId, task); // 后台异步执行任务,不阻塞主线程 _ = task.RunAsync(); await Task.CompletedTask; } // 停止用户任务 public void StopUserTask(string userId) { if (_userTasks.TryRemove(userId, out var task)) { task.Cancel(); } } // 添加用户连接 public async Task AddUserConnection(string userId, string connectionId) { var connections = _userConnections.GetOrAdd(userId, _ => new List<string>()); lock (connections) { if (!connections.Contains(connectionId)) connections.Add(connectionId); } // 用户重新连接时,发送当前任务状态 if (_userTasks.TryGetValue(userId, out var task)) { await _hubContext.Clients.Client(connectionId).SendAsync("ReceiveTaskUpdate", task.CurrentStatus); } } // 移除用户连接 public void RemoveUserConnection(string userId, string connectionId) { if (_userConnections.TryGetValue(userId, out var connections)) { lock (connections) { connections.Remove(connectionId); if (connections.Count == 0) _userConnections.TryRemove(userId, out _); } } } // 给用户发送更新 public async Task SendUpdateToUser(string userId, object updateData) { if (_userConnections.TryGetValue(userId, out var connections)) { foreach (var connId in connections) { await _hubContext.Clients.Client(connId).SendAsync("ReceiveTaskUpdate", updateData); } } } }
步骤3:实现用户后台任务逻辑
创建 UserBackgroundTask 封装具体的任务逻辑,定期执行并推送更新:
public class UserBackgroundTask { private readonly string _userId; private readonly CancellationToken _token; private readonly UserTaskManager _taskManager; // 当前任务状态,用于用户重新连接时同步 public object CurrentStatus { get; private set; } = new { Status = "Initializing...", Progress = 0 }; public UserBackgroundTask(string userId, CancellationToken token, UserTaskManager taskManager) { _userId = userId; _token = token; _taskManager = taskManager; } public async Task RunAsync() { try { int progress = 0; while (!_token.IsCancellationRequested) { // 模拟长期运行的任务逻辑(替换成你的业务代码) progress = Math.Min(progress + 10, 100); CurrentStatus = new { Status = progress < 100 ? $"Task in progress: {progress}%" : "Task completed!", Progress = progress }; // 推送更新到用户UI await _taskManager.SendUpdateToUser(_userId, CurrentStatus); // 模拟任务间隔(根据实际需求调整) await Task.Delay(2000, _token); if (progress >= 100) break; } } catch (OperationCanceledException) { CurrentStatus = new { Status = "Task cancelled.", Progress = 0 }; await _taskManager.SendUpdateToUser(_userId, CurrentStatus); } finally { // 任务结束后自动从管理器移除 _taskManager.StopUserTask(_userId); } } // 取消任务 public void Cancel() { (_token as CancellationTokenSource)?.Cancel(); } }
步骤4:后端API控制器
创建控制器处理用户的START/STOP请求:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; [ApiController] [Route("api/[controller]")] [Authorize] // 确保用户已登录 public class TaskController : ControllerBase { private readonly UserTaskManager _taskManager; public TaskController(UserTaskManager taskManager) { _taskManager = taskManager; } [HttpPost("Start")] public async Task<IActionResult> StartTask() { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) return Unauthorized(); await _taskManager.StartUserTask(userId); return Ok(); } [HttpPost("Stop")] public IActionResult StopTask() { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) return Unauthorized(); _taskManager.StopUserTask(userId); return Ok(); } }
步骤5:前端Razor页面实现
在Razor页面中添加按钮、状态显示区域,以及SignalR客户端逻辑:
@page @model IndexModel @{ ViewData["Title"] = "Long-Running Task Demo"; } <div class="container mt-5"> <h1>User-Specific Background Task</h1> <div class="mt-3"> <button id="startBtn" class="btn btn-primary">START</button> <button id="stopBtn" class="btn btn-danger ms-2" disabled>STOP</button> </div> <div id="taskStatus" class="mt-3 p-3 border rounded"></div> </div> <!-- 引入SignalR客户端脚本 --> <script src="~/lib/microsoft/signalr/dist/browser/signalr.js"></script> <script> // 初始化SignalR连接 const connection = new signalR.HubConnectionBuilder() .withUrl("/taskUpdateHub") .withAutomaticReconnect() // 断开自动重连 .build(); // 接收后端推送的更新 connection.on("ReceiveTaskUpdate", function(update) { const statusEl = document.getElementById("taskStatus"); statusEl.innerHTML = ` <p><strong>Status:</strong> ${update.Status}</p> <p><strong>Progress:</strong> ${update.Progress}%</p> `; // 更新按钮状态 if (update.Status === "Task completed!" || update.Status === "Task cancelled.") { document.getElementById("startBtn").disabled = false; document.getElementById("stopBtn").disabled = true; } }); // 启动SignalR连接 async function startConnection() { try { await connection.start(); console.log("SignalR connected"); } catch (err) { console.error(err); setTimeout(startConnection, 5000); // 重试连接 } } startConnection(); // START按钮点击事件 document.getElementById("startBtn").addEventListener("click", async function() { this.disabled = true; document.getElementById("stopBtn").disabled = false; const response = await fetch("/api/Task/Start", { method: "POST", headers: { "Content-Type": "application/json", "RequestVerificationToken": document.querySelector('input[name="__RequestVerificationToken"]').value } }); if (!response.ok) { alert("Failed to start task"); this.disabled = false; document.getElementById("stopBtn").disabled = true; } }); // STOP按钮点击事件 document.getElementById("stopBtn").addEventListener("click", async function() { this.disabled = true; document.getElementById("startBtn").disabled = false; const response = await fetch("/api/Task/Stop", { method: "POST", headers: { "Content-Type": "application/json", "RequestVerificationToken": document.querySelector('input[name="__RequestVerificationToken"]').value } }); if (!response.ok) { alert("Failed to stop task"); this.disabled = false; document.getElementById("startBtn").disabled = true; } }); // 页面关闭时断开连接 window.addEventListener("beforeunload", () => { connection.stop(); }); </script>
关键特性说明
- 用户任务隔离:通过用户ID绑定任务,每个用户的任务独立运行,更新仅推送给该用户的所有连接(支持多设备登录)。
- 实时更新:SignalR替代AJAX轮询,后端任务直接推送更新到前端,延迟更低。
- 状态续接:用户跳转页面返回后,SignalR自动重连,管理器会同步当前任务状态,用户继续接收后续更新。
- 任务生命周期控制:支持手动停止任务,任务完成/取消后自动清理资源。
内容的提问来源于stack exchange,提问作者Romen Klaassen
相关产品推荐
相关产品推荐

