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

Blazor中使用Task.WhenAll()执行同步任务时出现阻塞问题求助

Blazor任务同步阻塞问题的排查与修复

问题核心

你遇到的Task.WhenAll()阻塞问题,本质是进度检查任务的退出逻辑存在竞态,加上后台线程调用UI更新方法引发的上下文冲突,导致两个任务看似完成但主线程无法继续执行。

可能的原因

  1. 进度检查循环的竞态问题:同步任务完成后,进度检查任务可能正处于Task.Delay(3000)的等待中,即便ProductSuccessCount已经等于ProductCount,也无法及时退出循环。
  2. 后台线程调用UI更新的冲突:在Task.Run的后台线程中直接调用StateHasChanged(),Blazor的UI更新需要在同步上下文执行,后台线程调用会引发上下文阻塞,干扰任务完成信号的传递。
  3. 未处理的隐式异常:即便日志显示任务执行到最后一行,也可能存在未捕获的API请求异常,导致进度检查任务进入Faulted状态,进而让Task.WhenAll()看似阻塞。

修复方案

方案一:添加取消令牌,主动终止进度检查任务

通过取消令牌,在同步任务完成后主动终止进度检查,避免不必要的等待:

public async Task SyncBtnClick()
{
    string SyncId = Guid.NewGuid().ToString();
    ProductSuccessCount = 0;
    GetItemListDto = await ProductService.GetItemList(MallId);
    ProductCount = GetItemListDto.Response.TotalCount;
    SyncState = false;
    await ModalSync.Show();

    // 创建取消令牌源
    using var cts = new CancellationTokenSource();
    
    Task syncMallProductTask = Task.Run(async () =>
    {
        try
        {
            await ProductService.SyncMallProduct(SyncId, MallId);
        }
        finally
        {
            // 同步任务完成后,主动取消进度检查
            cts.Cancel();
        }
    });

    Task progressCheckTask = Task.Run(async () =>
    {
        try
        {
            while (!cts.Token.IsCancellationRequested)
            {
                if (ProductSuccessCount >= ProductCount)
                    break;
                    
                // 延迟时也监听取消信号
                await Task.Delay(3000, cts.Token);
                ProductSuccessCount = await _http.GetFromJsonAsync<int>($"api/Product/GetProgress?SyncGuid={SyncId}", cts.Token);
                // 确保在UI线程执行状态更新
                await InvokeAsync(StateHasChanged);
            }
        }
        catch (OperationCanceledException)
        {
            // 忽略取消异常,任务正常结束
        }
    }, cts.Token);
    
    await Task.WhenAll(syncMallProductTask, progressCheckTask);

    // 后续异常处理与业务逻辑不变
    if (syncMallProductTask.IsFaulted)
    {
        Log.Warning("syncMallProductTask任務出現異常: " + syncMallProductTask.Exception);
    }
    else if (progressCheckTask.IsFaulted)
    {
        Log.Warning("progressCheckTask任務出現異常: " + progressCheckTask.Exception);
    }
    else
    {
        Log.Information("子任務已完成");
        SyncState=true;
        await Task.Delay(3000);
        await MallService.MallsSyncInitList();
        Mall = MallService.Malls.ToList();
        StateHasChanged();
    }
}

方案二:移除不必要的Task.Run,利用Blazor异步上下文

Blazor组件的异步方法本身运行在UI上下文,无需额外用Task.Run包装异步IO任务,减少上下文切换开销:

public async Task SyncBtnClick()
{
    string SyncId = Guid.NewGuid().ToString();
    ProductSuccessCount = 0;
    GetItemListDto = await ProductService.GetItemList(MallId);
    ProductCount = GetItemListDto.Response.TotalCount;
    SyncState = false;
    await ModalSync.Show();

    // 直接调用异步方法,无需Task.Run
    Task syncMallProductTask = ProductService.SyncMallProduct(SyncId, MallId);
    Task progressCheckTask = CheckProgressAsync(SyncId);
    
    await Task.WhenAll(syncMallProductTask, progressCheckTask);

    // 后续处理逻辑不变...
}

// 单独提取进度检查方法,运行在UI上下文
private async Task CheckProgressAsync(string syncId)
{
    while (ProductSuccessCount < ProductCount)
    {
        await Task.Delay(3000);
        ProductSuccessCount = await _http.GetFromJsonAsync<int>($"api/Product/GetProgress?SyncGuid={syncId}");
        // 此时在UI上下文,直接调用StateHasChanged即可
        StateHasChanged();
    }
}

关键注意事项

  • UI更新必须在UI线程执行:如果在后台线程中需要更新UI,必须使用InvokeAsync(StateHasChanged),避免上下文冲突。
  • 取消令牌的使用:添加取消机制能确保任务之间的协作,避免无意义的等待。
  • 避免滥用Task.Run:Blazor中仅在处理CPU密集型操作时才需要用Task.Run,异步IO任务直接调用即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 23:54:54