You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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);
}

三、额外注意事项

  1. 避免长时间请求占用连接:如果SQL操作超过30分钟,建议考虑用**后台任务框架(如Hangfire)**异步执行,前端通过轮询或SignalR接收进度/完成通知,这比长时间保持HTTP连接更可靠。
  2. SQL优化:优先优化高耗时SQL(如加索引、分批次执行),从根源减少操作时间,比单纯异步化效果更好。
  3. 前端防抖:通过isProcessing变量防止用户重复点击触发多次请求,避免服务器压力过大。

内容的提问来源于stack exchange,提问作者cdub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 02:50:33