.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
相关产品推荐
相关产品推荐

