读取/proc/stat计算CPU使用率触发DivideByZeroException问题求助
Let's break down the issues in your code and fix them step by step:
1. Asynchronous Delay Issue Causing Zero Total Difference
Your current code uses Task.Delay(200) followed by a recursive call to GetCPUUsage(), but Task.Delay is asynchronous—it doesn't block the current thread. This means the recursive call runs immediately after starting the delay, so you're reading the /proc/stat data twice in rapid succession. The CPU stats won't change in that tiny window, leading to totalDifference = 0 and the divide-by-zero error.
Fix: Make GetCPUUsage an asynchronous method, await the delay, and avoid recursion. Also, ensure you only read the stats once per sample interval.
2. Incorrect CPU Usage Formula
Your calculation line has a syntax error in parentheses, which would lead to wrong values even when there's no divide-by-zero:
decimal cpuPercentage = (totalDifference - idleDifference / totalDifference) * 100;
The correct formula (from the logic you referenced) is:
decimal cpuPercentage = ((totalDifference - idleDifference) / totalDifference) * 100;
You need to calculate the difference between total and idle first, then divide by the total difference.
3. Flawed /proc/stat Parsing Logic
Your SaveIntsFromFilePath method has a few critical issues:
- The
i > 5check is arbitrary and might skip the first set of CPU stats (the first line starts withcpu, so the first number starts at index 4). - It doesn't handle consecutive whitespace (common in
/proc/statlines), which would result in empty strings being parsed (throwing exceptions) or missing values. - You're appending to an existing list without clearing it first, so subsequent calls will keep adding more data, leading to incorrect index references (like
oldCpuStatistics[3]pointing to wrong values).
Fix: Simplify parsing by splitting the line into tokens, filtering out non-numeric values, and replacing the list instead of appending.
4. Missing Reset of Statistics Lists
After calculating the CPU usage, you don't update oldCpuStatistics with the new values, and you don't clear newCpuStatistics. This means subsequent runs will use stale data, leading to incorrect calculations or exceptions.
Corrected Code
Here's the revised version of your class with all fixes applied:
public class HardwareInfoManager : IHardwareInfoManager { private readonly IConfiguration _configuration; private List<long> _oldCpuStats; public HardwareInfoManager(IConfiguration configuration) { _configuration = configuration; _oldCpuStats = new List<long>(); } public async Task<decimal> GetCPUUsageAsync() { string cpuStatPath = "/proc/stat"; if (!File.Exists(cpuStatPath)) return 0; // First run: initialize old stats and wait for interval if (_oldCpuStats.Count == 0) { _oldCpuStats = ParseCpuStats(cpuStatPath); await Task.Delay(200); // Wait for stats to change return await GetCPUUsageAsync(); } // Get new stats var newCpuStats = ParseCpuStats(cpuStatPath); // Validate we have enough data points if (newCpuStats.Count < 8) return 0; // Calculate idle and non-idle times long prevIdle = _oldCpuStats[3] + _oldCpuStats[4]; long idle = newCpuStats[3] + newCpuStats[4]; long prevNonIdle = _oldCpuStats[0] + _oldCpuStats[1] + _oldCpuStats[2] + _oldCpuStats[5] + _oldCpuStats[6] + _oldCpuStats[7]; long nonIdle = newCpuStats[0] + newCpuStats[1] + newCpuStats[2] + newCpuStats[5] + newCpuStats[6] + newCpuStats[7]; long prevTotal = prevIdle + prevNonIdle; long total = idle + nonIdle; long totalDiff = total - prevTotal; long idleDiff = idle - prevIdle; // Avoid divide by zero if (totalDiff == 0) return 0; decimal cpuPercentage = ((totalDiff - idleDiff) / (decimal)totalDiff) * 100; cpuPercentage = Math.Round(cpuPercentage, 2); // Update old stats for next run _oldCpuStats = newCpuStats; return cpuPercentage; } private List<long> ParseCpuStats(string path) { var cpuLine = File.ReadAllLines(path).FirstOrDefault(line => line.StartsWith("cpu ")); if (string.IsNullOrEmpty(cpuLine)) return new List<long>(); // Split into tokens, filter out non-numeric, parse to long return cpuLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) .Skip(1) // Skip the "cpu" prefix .Where(token => long.TryParse(token, out _)) .Select(long.Parse) .ToList(); } }
Additional Recommendations
- Avoid Recursion: The recursive call in the original code could lead to stack overflow if called repeatedly. The async approach with
awaitis safer. - Add Error Handling: Wrap file operations in
try-catchblocks to handle cases where/proc/statis temporarily unavailable (unlikely on Linux, but possible in edge cases). - Validate Data: Always check that the parsed stats list has enough elements before accessing indexes to prevent
IndexOutOfRangeException. - Use
readonlyFields: Mark fields like_configurationand_oldCpuStatsasreadonlywhere appropriate for better immutability.
内容的提问来源于stack exchange,提问作者grozdeto

