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

Blazor Server执行后台任务时避免UI冻结与重连问题

解决Blazor Server大量异步任务导致UI冻结的方案

一、修复核心异步逻辑问题

1. 替换同步阻塞为异步等待

Demo类中Thread.Sleep会阻塞线程池线程,导致线程资源被快速耗尽,换成异步等待的Task.Delay,让线程可以被线程池复用:

public class Demo
{
    public int Id;
    public bool WorkDone { get; set; }

    public Demo(int id)
    {
        this.WorkDone = false;
        this.Id = id;
    }

    public async Task Work()
    {
        Random r = new();
        await Task.Delay(1000 * r.Next(1, 10)); // 异步等待,不占用线程
        this.WorkDone = true;
    }
}

同时去掉原Work方法中多余的Task.Run包装,因为Work本身已经是异步方法。

2. 用异步限流替代AsParallel

AsParallel().ForAll不兼容异步委托,会导致线程池滥用和异步任务失控。改用SemaphoreSlim限制并发数,同时用异步循环处理任务:

@code {
    // ... 其他代码不变

    // 改为异步方法,Blazor支持绑定异步点击事件
    public async Task DoWork()
    {
        allworkdone = false;
        await InvokeAsync(StateHasChanged);
        
        // 限制同时运行10个任务,可根据服务器配置调整(比如CPU核心数*2)
        var semaphore = new SemaphoreSlim(10);
        var tasks = new List<Task>();
        
        foreach (var item in data)
        {
            await semaphore.WaitAsync();
            tasks.Add(Task.Run(async () =>
            {
                try
                {
                    await item.Work();
                }
                finally
                {
                    semaphore.Release();
                    await InvokeAsync(StateHasChanged); // 单个任务完成后更新UI
                }
            }));
        }
        
        await Task.WhenAll(tasks);
        allworkdone = true;
        await InvokeAsync(StateHasChanged);
    }
}

二、优化UI更新频率

10000个任务每个都触发一次UI更新会造成大量重渲染开销,可以改为批量更新:

public async Task DoWork()
{
    allworkdone = false;
    await InvokeAsync(StateHasChanged);
    
    int completedCount = 0;
    var semaphore = new SemaphoreSlim(10);
    var tasks = new List<Task>();
    
    foreach (var item in data)
    {
        await semaphore.WaitAsync();
        tasks.Add(Task.Run(async () =>
        {
            try
            {
                await item.Work();
                // 原子操作计数,避免多线程冲突
                Interlocked.Increment(ref completedCount);
                // 每10个任务完成后统一更新UI
                if (completedCount % 10 == 0)
                {
                    await InvokeAsync(StateHasChanged);
                }
            }
            finally
            {
                semaphore.Release();
            }
        }));
    }
    
    await Task.WhenAll(tasks);
    allworkdone = true;
    await InvokeAsync(StateHasChanged);
}

三、避免线程池耗尽的关键注意事项

  • 仅在处理CPU密集型任务时使用Task.Run,IO密集型任务直接调用原生异步API即可,不要额外包装。
  • 合理设置并发数:根据服务器CPU核心数调整SemaphoreSlim的初始计数,避免线程池过载。
  • 禁止在同步生命周期方法(如OnInitialized)中执行阻塞操作,所有耗时逻辑都要放在异步方法里。

四、修正原代码的逻辑错误

原代码中allworkdone = true;会在AsParallel.ForAll执行完成后立刻设置,但此时异步任务还在后台运行,导致"Work Done"提示过早出现。必须放在Task.WhenAll之后,才能确保所有任务真正完成。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 12:05:42