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批量处理。
- 解决:在合理范围内并行处理文件(需控制并发度,避免数据库连接、磁盘IO耗尽),比如使用
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
相关产品推荐
相关产品推荐

