.NET Core Razor局部页面:基于Async/Await优化Fetch调用长耗时SQL任务
解决方案:用Async/Await优化.NET Core Razor高耗时操作的UI更新
针对高耗时SQL操作导致UI无法正常更新的问题,核心是通过异步化后端操作释放服务器线程、异步化前端请求流避免UI阻塞,同时配合状态反馈提升用户体验。
一、后端:将同步SQL操作改为异步实现
Razor页面的Handler必须改为异步方法,用async/await执行SQL操作,避免阻塞服务器线程,确保请求能正常完成。
修改后的Razor页面Handler示例
// ProcessCode.cshtml.cs public class ProcessCodeModel : PageModel { private readonly YourDbContext _dbContext; private readonly ILogger<ProcessCodeModel> _logger; public ProcessCodeModel(YourDbContext dbContext, ILogger<ProcessCodeModel> logger) { _dbContext = dbContext; _logger = logger; } // 异步Handler:启动流程 public async Task<IActionResult> OnGetStartProcessPartial() { try { // 用EF Core异步方法执行高耗时SQL var processData = await _dbContext.LongRunningOperations .Where(x => x.Status == "Pending") .ToListAsync(); // 原生SQL异步执行示例 // using var command = _dbContext.Database.GetDbConnection().CreateCommand(); // command.CommandText = "YOUR_LONG_RUNNING_SQL_QUERY"; // await command.Connection.OpenAsync(); // using var reader = await command.ExecuteReaderAsync(); // 读取并处理结果... return Partial("_StartProcessPartial", processData); } catch (Exception ex) { _logger.LogError(ex, "启动流程失败"); return new EmptyResult(); } } // 异步Handler:第二步操作 public async Task<IActionResult> OnGetStep2Partial() { try { var step2Data = await _dbContext.Step2Operations.ToListAsync(); return Partial("_Step2Partial", step2Data); } catch (Exception ex) { _logger.LogError(ex, "第二步执行失败"); return new EmptyResult(); } } }
后端关键配置(解决长时间请求超时)
由于操作耗时可达1小时,需调整ASP.NET Core的请求超时限制,在Program.cs中添加:
var builder = WebApplication.CreateBuilder(args); // 调整Kestrel服务器超时 builder.Services.Configure<KestrelServerOptions>(options => { options.Limits.KeepAliveTimeout = TimeSpan.FromHours(1); }); // 若使用IIS部署,调整IIS服务器超时 builder.Services.Configure<IISServerOptions>(options => { options.ConnectionTimeout = TimeSpan.FromHours(1); }); // 其他配置...
二、前端:用Async/Await重构请求流,添加状态反馈
将原有的then链式调用改为async/await写法,让流程更清晰;同时添加加载状态、错误提示,避免用户误操作,确保UI能在操作完成后正确更新。
修改后的JavaScript代码
// 全局状态:防止重复触发流程 let isProcessing = false; // 启动流程入口函数 async function startProcess() { if (isProcessing) return; isProcessing = true; // 显示加载指示器 toggleLoading(true); // 隐藏之前的错误信息 hideError(); try { // 给请求添加1小时超时(防止前端请求中断) const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3600000); // 3600秒=1小时 const response = await fetch('processcode/?handler=StartProcessPartial', { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`请求失败:${response.status} ${response.statusText}`); } const result = await response.text(); if (result.trim().length === 0) { showError("流程启动失败,未获取到有效数据"); return; } // 更新第一步UI document.getElementById("step1Container").innerHTML = result; scrollBottom(); // 等待第二步完成后再继续 await getStep2(); } catch (error) { if (error.name !== "AbortError") { showError(`启动流程出错:${error.message}`); console.error(error); } } finally { isProcessing = false; toggleLoading(false); } } // 第二步流程函数 async function getStep2() { if (isProcessing) return; isProcessing = true; toggleLoading(true); hideError(); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3600000); const response = await fetch('processcode/?handler=Step2Partial', { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`第二步请求失败:${response.status} ${response.statusText}`); } const result = await response.text(); if (result.trim().length === 0) { showError("第二步执行失败"); return; } // 更新第二步UI document.getElementById("step2Container").innerHTML = result; scrollBottom(); // 依次调用后续步骤(如getStep3()) // await getStep3(); } catch (error) { if (error.name !== "AbortError") { showError(`第二步出错:${error.message}`); console.error(error); } } finally { isProcessing = false; toggleLoading(false); } } // 辅助函数:显示/隐藏加载指示器 function toggleLoading(show) { const loader = document.getElementById("loadingIndicator"); if (loader) { loader.style.display = show ? "block" : "none"; } } // 辅助函数:显示错误信息 function showError(message) { const errorEl = document.getElementById("errorMessage"); if (errorEl) { errorEl.textContent = message; errorEl.style.display = "block"; } } // 辅助函数:隐藏错误信息 function hideError() { const errorEl = document.getElementById("errorMessage"); if (errorEl) { errorEl.style.display = "none"; } } // 保持原有滚动逻辑 function scrollBottom() { window.scrollTo(0, document.body.scrollHeight); }
三、额外注意事项
- 避免长时间请求占用连接:如果SQL操作超过30分钟,建议考虑用**后台任务框架(如Hangfire)**异步执行,前端通过轮询或SignalR接收进度/完成通知,这比长时间保持HTTP连接更可靠。
- SQL优化:优先优化高耗时SQL(如加索引、分批次执行),从根源减少操作时间,比单纯异步化效果更好。
- 前端防抖:通过
isProcessing变量防止用户重复点击触发多次请求,避免服务器压力过大。
内容的提问来源于stack exchange,提问作者cdub
相关产品推荐
相关产品推荐

