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

ASP.NET Core 6 MVC中LowLevelLifoSemaphore引发高CPU占用求助

高CPU占用排查:system.Threading.LowLevelLifoSemaphore.WaitForSignal(int) 关联多图片上传场景

在C# ASP.NET Core 6 MVC开发中遇到高CPU占用问题,通过dotnet-trace排查发现system.Threading.LowLevelLifoSemaphore.WaitForSignal(int)是主要诱因,推测与多图片上传场景相关。项目功能为接收移动端上传的图片,将图片及数据存储至Linux服务器与数据库,以下为相关代码,求排查方向或解决办法。

相关代码

[Route("routToWeb")]
public async Task<string> saveImages()
{            
    try
    {
        string[] dividirFilePath, fechaSinPunto;
        string dir = DateTime.Now.ToString("yyyy/MM/dd");
        string ruta, consulta, strCompleta;
        List<PedidoViewModel> verificarDato;
        var dirPath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/imgAndroid/", dir);

        if (!Directory.Exists(dirPath))
        {
            Directory.CreateDirectory(dirPath);
        }

        string[] dividir, remitoDeVuelta;
        int cont = 0;
        while ((cont + 1) <= this.Request.Form.Files.Count)
        {
            try
            {
                var fileName = this.Request.Form.Files[cont].FileName; 
                if (fileName.Contains("/Pictures/"))
                {
                    dividirFilePath  = fileName.Split("Pictures/");
                    fileName = dividirFilePath[1];
                }

                strCompleta = dir + "/" + fileName;

                var filePath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/imgAndroid/", strCompleta);

                if (filePath.Contains("/Pictures/"))
                {
                    dividirFilePath = filePath.Split("/storage/emulated/0/Pictures/");
                    filePath = dividirFilePath[0] + dividirFilePath[1];
                }

                dividir = fileName.Split("_");

                // dividir[2] = dividir[2].Remove(4);
                remitoDeVuelta = dividir[2].Split("-");

                verificarDato = new List<PedidoViewModel>();
                consulta = "SELECT p.Pedido, p.Entrega, p.Remito FROM Pedido p WHERE Remito = '" + remitoDeVuelta[0] + remitoDeVuelta[1] + "' AND Entrega = '" + dividir[1] + "';";
                verificarDato = VerificarDato(consulta);

                if (verificarDato.Count == 0)
                {
                    consulta = "INSERT INTO Pedido (Pedido, Entrega, Remito) VALUES " + " ('" + dividir[0] + "','" + dividir[1] + "','" + remitoDeVuelta[0] + remitoDeVuelta[1] + "');";

                    if (!InsertConexionMySql(consulta)) return "fallo en la subida BD";
                }

                ruta = "/imgAndroid/" + strCompleta;
                
                fechaSinPunto = dividir[4].Split(".");
                consulta = "INSERT INTO Entrega (Entrega, Path, Fecha, NombreUsuario, GrupoUsuario) VALUES" + " ('" + dividir[1] + "','" + ruta + "','" + dividir[3] + " " + fechaSinPunto[0] + "','-','-');";

                if (!InsertConexionMySql(consulta)) return "fallo en la subida BD";

                using (var stream = System.IO.File.Create(filePath))
                {
                    await this.Request.Form.Files[cont].CopyToAsync(stream);
                }
                cont++;                        
            }
            catch (Exception ex)
            {
                Logger.Error(ex.Message + " || Funcion: Post() 1er catch");
                return "Subida fallida." + ex.ToString();
            }

        }
        Logger.Debug("Posteo de imagenes subida exitosa || Funcion: Post()");
        return "Subida exitosa";
    }
    catch (Exception ex)
    {

        Logger.Error(ex.Message + " || Funcion: Post() 2do catch");
        return "fallo en la subida.";
    }
}

排查方向与解决办法

1. 修复同步数据库操作的线程阻塞问题

  • VerificarDato和InsertConexionMySql是同步方法,在异步控制器中调用会阻塞线程池线程,导致线程池频繁扩容/收缩,进而引发LowLevelLifoSemaphore的信号等待CPU开销。
    • 解决:将数据库操作改为异步版本(如MySqlCommand.ExecuteReaderAsync、ExecuteNonQueryAsync),保证整个调用链全异步,避免线程阻塞。
  • 代码存在SQL注入风险,且字符串拼接SQL会增加CPU开销、无法复用数据库执行计划。
    • 解决:改用参数化查询,同时提升数据库执行效率。

2. 优化文件IO操作

  • File.Create是同步方法,即使后续用了CopyToAsync,创建文件的同步操作仍会阻塞线程。
    • 解决:用File.CreateAsync替代File.Create,保持IO操作全程异步。
  • 当前串行处理文件,高并发上传时会导致请求排队,加剧线程池压力。
    • 解决:在合理范围内并行处理文件(需控制并发度,避免数据库连接、磁盘IO耗尽),比如使用Task.WhenAll批量处理。

3. 调整线程池配置

  • ASP.NET Core默认线程池配置在高并发IO场景下可能不足,导致线程等待信号的开销增加。
    • 解决:在Program.cs中调整线程池最小线程数(根据服务器CPU核心数调整,避免过大导致上下文切换开销):
      ThreadPool.SetMinThreads(workerThreads: 100, completionPortThreads: 100);
      

4. 简化文件路径处理逻辑

  • 代码中多次重复对路径进行Split和拼接,增加不必要的CPU开销。
    • 解决:用Path.GetFileName直接获取文件名,统一处理移动端路径前缀,减少重复判断与字符串操作。

5. 优化异常处理逻辑

  • 内层catch直接return,单个文件失败会中断整个请求,且日志仅记录消息,缺少堆栈信息不利于排查。
    • 解决:单个文件失败时跳过并记录完整异常(含堆栈),继续处理其他文件,避免过早中断流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 15:25:17