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

.NET 8 Blazor Interactive Server为何需用await Task.Delay(1)实现页面加载?

Blazor页面跳转与加载体验优化问题解答

问题背景

在Blazor组件的OnInitializedAsync方法中添加await Task.Delay(1);时,点击跳转按钮会立即进入目标页面并显示加载器;但去掉该延迟后,必须等待数据加载完成才会跳转,用户体验较差。相关代码如下:

组件初始化代码

protected override async Task OnInitializedAsync()
{
    await Task.Delay(1);
    Criterias = new ParcoursCriterias()
    {
        PageNumber = pageNumber,
        ElementsByPage = 10
    };
    ParcoursList = ParcoursService.GetAll(Criterias);
}

App.razor代码

@if(RenderModeForPage is not null)
    {
        <HeadOutlet @rendermode="@RenderModeForPage" />    
    }
    else
    {
        <HeadOutlet />
    }
</head>

<body>
    @if(RenderModeForPage is not null)
    {
        <Routes @rendermode="@RenderModeForPage" />
    }
    else
    {
        <Routes />
    }
    <script src="_framework/blazor.web.js"></script>
    <script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>

</html>

@code {
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
        ? null
        : InteractiveServer;
}

服务层数据加载代码

public ParcoursDtoList GetAll(ParcoursCriterias criterias)
{
    _logger.LogInformation($"Obtenir plusieurs Parcours: #page {criterias.PageNumber} par l'utilisateur: userId"); // TODO: mettre userID

    var query = _context.Parcours
        .AsQueryable()
        .AsNoTracking();

    var comments = Filter(query, criterias);

    return comments;
}

private ParcoursDtoList Filter(IQueryable<ParcoursBd> query, ParcoursCriterias criterias)
{
    query = query
        .OrderByDescending(c => c.Creation);


    if (criterias.Name != null)
    {
        query = query.Where(p => p.Name == criterias.Name);
    }
    if (criterias.Description != null)
    {
        query = query.Where(p => criterias.Description.Contains(p.Description));
    }

    var totalPages = (int)Math.Ceiling(query.Count() / (decimal)criterias.ElementsByPage);

    if ((criterias.PageNumber <= 0 || criterias.PageNumber > totalPages) && totalPages > 0)
        throw new PageNumberNotInRangeException(criterias.PageNumber, totalPages);

    query = Paginate(query, criterias);

    return new ParcoursDtoList()
    {
        ListParcours = _mapper.Map<List<ParcoursBd>, List<ParcoursDto>>(query.ToList()),
        PageNumber = criterias.PageNumber,
        TotalPages = totalPages,
        ElementsByPage = criterias.ElementsByPage
    };
}

private IQueryable<ParcoursBd> Paginate(IQueryable<ParcoursBd> query, ParcoursCriterias criterias)
{
    int startIndex = (criterias.PageNumber - 1) * criterias.ElementsByPage;

    return query.Skip(startIndex).Take(criterias.ElementsByPage);
}

Task.Delay(1)的做法是否正确?

这种做法能临时解决问题,但并非规范的长期方案。Task.Delay(1)的本质是让当前异步方法让出线程,给Blazor渲染管线留出时间完成页面初始渲染(包括加载器),再执行数据加载。但依赖固定延迟不可靠:不同环境下渲染耗时不同,延迟太短可能达不到效果,太长又会增加无意义的等待时间。


更合适的实现方式

1. 数据加载异步化(推荐方案)

服务层的GetAll是同步方法,会阻塞渲染线程。改成异步版本,适配Blazor的异步渲染机制:

修改后的服务层代码

public async Task<ParcoursDtoList> GetAllAsync(ParcoursCriterias criterias)
{
    _logger.LogInformation($"Obtenir plusieurs Parcours: #page {criterias.PageNumber} par l'utilisateur: userId"); // TODO: mettre userID

    var query = _context.Parcours
        .AsQueryable()
        .AsNoTracking();

    return await FilterAsync(query, criterias);
}

private async Task<ParcoursDtoList> FilterAsync(IQueryable<ParcoursBd> query, ParcoursCriterias criterias)
{
    query = query.OrderByDescending(c => c.Creation);

    if (criterias.Name != null)
    {
        query = query.Where(p => p.Name == criterias.Name);
    }
    if (criterias.Description != null)
    {
        query = query.Where(p => criterias.Description.Contains(p.Description));
    }

    var totalCount = await query.CountAsync();
    var totalPages = (int)Math.Ceiling(totalCount / (decimal)criterias.ElementsByPage);

    if ((criterias.PageNumber <= 0 || criterias.PageNumber > totalPages) && totalPages > 0)
        throw new PageNumberNotInRangeException(criterias.PageNumber, totalPages);

    query = Paginate(query, criterias);
    var dataList = await query.ToListAsync();

    return new ParcoursDtoList()
    {
        ListParcours = _mapper.Map<List<ParcoursBd>, List<ParcoursDto>>(dataList),
        PageNumber = criterias.PageNumber,
        TotalPages = totalPages,
        ElementsByPage = criterias.ElementsByPage
    };
}

private IQueryable<ParcoursBd> Paginate(IQueryable<ParcoursBd> query, ParcoursCriterias criterias)
{
    int startIndex = (criterias.PageNumber - 1) * criterias.ElementsByPage;
    return query.Skip(startIndex).Take(criterias.ElementsByPage);
}

修改后的组件代码

protected override async Task OnInitializedAsync()
{
    Criterias = new ParcoursCriterias()
    {
        PageNumber = pageNumber,
        ElementsByPage = 10
    };
    ParcoursList = await ParcoursService.GetAllAsync(Criterias);
}

异步化后,渲染线程不会被阻塞,Blazor会先渲染加载状态,待数据加载完成后自动更新UI。

2. 手动触发渲染(适配无法修改服务层的场景)

如果暂时无法将服务层改为异步,可在数据加载前主动通知Blazor完成一次渲染:

protected override async Task OnInitializedAsync()
{
    Criterias = new ParcoursCriterias()
    {
        PageNumber = pageNumber,
        ElementsByPage = 10
    };
    // 先触发渲染,显示加载状态
    await InvokeAsync(StateHasChanged);
    // 再执行同步数据加载
    ParcoursList = ParcoursService.GetAll(Criterias);
}

这种方式比Task.Delay更可靠,因为InvokeAsync(StateHasChanged)是明确通知Blazor渲染当前状态,不依赖随机延迟。

3. 显式管理加载状态

在组件中维护IsLoading变量,UI根据该变量切换加载器和数据视图:

@if (IsLoading)
{
    <div>加载中...</div> <!-- 可替换为MudBlazor的MudProgressCircular等组件 -->
}
else
{
    <!-- 渲染ParcoursList数据的UI -->
}

@code {
    private bool IsLoading = true;
    private ParcoursDtoList? ParcoursList;
    private ParcoursCriterias? Criterias;

    protected override async Task OnInitializedAsync()
    {
        Criterias = new ParcoursCriterias()
        {
            PageNumber = pageNumber,
            ElementsByPage = 10
        };
        // 先渲染加载状态
        await InvokeAsync(StateHasChanged);
        // 加载数据
        ParcoursList = ParcoursService.GetAll(Criterias);
        IsLoading = false;
    }
}

总结

  • Task.Delay(1)是临时 workaround,不推荐作为长期方案;
  • 最优解是将数据访问改为异步,符合Blazor的异步渲染模型;
  • 若无法异步化,用InvokeAsync(StateHasChanged)手动触发渲染比依赖延迟更可靠;
  • 配合显式的加载状态变量,能让用户体验更清晰可控。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:13:15