1. WPF中获取系统存储与内存信息的完整实现方案
在桌面应用开发中,经常需要获取当前设备的硬件信息用于资源监控、系统诊断或容量展示。作为.NET平台下的桌面应用框架,WPF提供了多种方式获取系统存储和内存信息。本文将详细介绍最可靠的实现方法,并分享实际项目中的优化经验。
1.1 基础实现原理
获取系统信息主要依赖两个.NET类库:
System.IO.DriveInfo:用于查询磁盘存储空间System.Diagnostics.PerformanceCounter:用于监测内存使用情况
这两种方式都是通过Windows Management Instrumentation (WMI)底层接口获取数据,具有较高的准确性和实时性。不同于直接调用WMI接口,这些封装好的类库使用更简便且性能开销更小。
注意:使用PerformanceCounter需要添加对System.Diagnostics.PerformanceCounter程序集的引用,在.NET Core/.NET 5+中需要单独安装System.Diagnostics.PerformanceCounter包
1.2 完整代码实现与解析
以下是获取存储和内存信息的完整实现,包含详细的注释说明:
csharp复制public class SystemResourceInfo
{
/// <summary>
/// 总内存(GB)
/// </summary>
public double TotalMemoryGB { get; set; }
/// <summary>
/// 已用内存(GB)
/// </summary>
public double UsedMemoryGB { get; set; }
/// <summary>
/// 总存储(GB)
/// </summary>
public double TotalStorageGB { get; set; }
/// <summary>
/// 已用存储(GB)
/// </summary>
public double UsedStorageGB { get; set; }
/// <summary>
/// 获取当前系统资源信息
/// </summary>
public void RefreshSystemInfo()
{
GetStorageInfo();
GetMemoryInfo();
}
private void GetStorageInfo(string driveLetter = null)
{
// 默认获取应用程序所在盘符信息
driveLetter = driveLetter ?? Path.GetPathRoot(AppDomain.CurrentDomain.BaseDirectory);
try
{
DriveInfo drive = new(driveLetter);
TotalStorageGB = BytesToGB(drive.TotalSize);
UsedStorageGB = BytesToGB(drive.TotalSize - drive.AvailableFreeSpace);
}
catch (Exception ex)
{
// 处理异常情况,如盘符不存在等
Debug.WriteLine($"获取存储信息失败: {ex.Message}");
TotalStorageGB = 0;
UsedStorageGB = 0;
}
}
private void GetMemoryInfo()
{
try
{
// 第一次调用NextValue()可能返回0,需要间隔后再次获取
var committedCounter = new PerformanceCounter("Memory", "Committed Bytes");
var availableCounter = new PerformanceCounter("Memory", "Available Bytes");
// 适当延迟确保获取准确值
Thread.Sleep(500);
double committedBytes = committedCounter.NextValue();
double availableBytes = availableCounter.NextValue();
TotalMemoryGB = BytesToGB(committedBytes);
UsedMemoryGB = TotalMemoryGB - BytesToGB(availableBytes);
}
catch (Exception ex)
{
Debug.WriteLine($"获取内存信息失败: {ex.Message}");
TotalMemoryGB = 0;
UsedMemoryGB = 0;
}
}
/// <summary>
/// 字节转换为GB的通用方法
/// </summary>
private double BytesToGB(double bytes)
{
const double GB = 1024 * 1024 * 1024;
return Math.Round(bytes / GB, 2);
}
}
2. 关键实现细节与优化建议
2.1 存储信息获取的注意事项
- 多磁盘支持:
- 上述代码默认获取应用程序所在盘符信息
- 可通过
DriveInfo.GetDrives()获取所有磁盘信息 - 指定特定盘符时需验证有效性
csharp复制// 获取所有磁盘信息示例
public List<(string DriveName, double TotalGB, double UsedGB)> GetAllDrivesInfo()
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady)
.Select(d => (
d.Name,
BytesToGB(d.TotalSize),
BytesToGB(d.TotalSize - d.AvailableFreeSpace)
))
.ToList();
}
- 异常处理:
- 移动设备可能随时断开连接
- 网络驱动器可能存在延迟
- 需要处理
DriveNotReadyException等异常
2.2 内存信息获取的优化方案
- 性能计数器初始化问题:
PerformanceCounter首次调用NextValue()可能返回0- 需要适当延迟后再次获取
- 建议封装为可重试的方法
csharp复制private double GetPerformanceCounterValue(PerformanceCounter counter, int retryCount = 2)
{
double value = counter.NextValue();
int retry = 0;
while (value <= 0 && retry < retryCount)
{
Thread.Sleep(500);
value = counter.NextValue();
retry++;
}
return value;
}
- 内存计算准确性:
- "Committed Bytes"表示系统已提交的内存
- "Available Bytes"表示可立即使用的物理内存
- 实际使用量 = 已提交内存 - 可用内存
2.3 单位转换的通用工具类
将字节转换为GB的逻辑可以封装为静态工具类:
csharp复制public static class UnitConverter
{
private const double GB = 1024 * 1024 * 1024;
private const double MB = 1024 * 1024;
private const double KB = 1024;
public static double BytesToGB(double bytes) => Math.Round(bytes / GB, 2);
public static double BytesToMB(double bytes) => Math.Round(bytes / MB, 2);
public static double BytesToKB(double bytes) => Math.Round(bytes / KB, 2);
public static string ToHumanReadableSize(double bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
int order = 0;
while (bytes >= 1024 && order < sizes.Length - 1)
{
order++;
bytes /= 1024;
}
return $"{Math.Round(bytes, 2)} {sizes[order]}";
}
}
3. WPF界面集成与实时更新
3.1 数据绑定实现
在WPF中,可以通过数据绑定将系统资源信息实时显示在界面上:
- 实现INotifyPropertyChanged接口:
csharp复制public class SystemResourceInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _totalMemoryGB;
public double TotalMemoryGB
{
get => _totalMemoryGB;
set
{
_totalMemoryGB = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TotalMemoryGB)));
}
}
// 其他属性同理...
}
- XAML中的数据绑定:
xml复制<StackPanel>
<TextBlock Text="{Binding TotalMemoryGB, StringFormat='总内存: {0} GB'}"/>
<TextBlock Text="{Binding UsedMemoryGB, StringFormat='已用内存: {0} GB'}"/>
<ProgressBar Value="{Binding UsedMemoryGB}"
Maximum="{Binding TotalMemoryGB}"
Height="20" Margin="0,5"/>
<TextBlock Text="{Binding TotalStorageGB, StringFormat='总存储: {0} GB'}"/>
<TextBlock Text="{Binding UsedStorageGB, StringFormat='已用存储: {0} GB'}"/>
<ProgressBar Value="{Binding UsedStorageGB}"
Maximum="{Binding TotalStorageGB}"
Height="20" Margin="0,5"/>
</StackPanel>
3.2 定时刷新机制
要实现资源的实时监控,可以添加定时刷新功能:
csharp复制public class SystemMonitorViewModel : INotifyPropertyChanged
{
private readonly SystemResourceInfo _resourceInfo = new();
private readonly DispatcherTimer _refreshTimer;
public SystemMonitorViewModel()
{
// 初始化定时器,每2秒刷新一次
_refreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(2)
};
_refreshTimer.Tick += (s, e) => _resourceInfo.RefreshSystemInfo();
_refreshTimer.Start();
// 初始刷新
_resourceInfo.RefreshSystemInfo();
}
// 将资源信息属性暴露给视图
public double TotalMemoryGB => _resourceInfo.TotalMemoryGB;
public double UsedMemoryGB => _resourceInfo.UsedMemoryGB;
// 其他属性...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
4. 常见问题与解决方案
4.1 PerformanceCounter权限问题
在某些系统配置下,访问性能计数器可能需要管理员权限。如果遇到以下问题:
-
症状:
- 抛出"拒绝访问"异常
- 返回的数据始终为0
-
解决方案:
- 以管理员身份运行应用程序
- 或修改注册表权限(不推荐生产环境使用)
csharp复制// 检查性能计数器是否可用的方法
public static bool IsPerformanceCounterAccessible()
{
try
{
using var counter = new PerformanceCounter("Memory", "Available Bytes");
var value = counter.NextValue();
return true;
}
catch
{
return false;
}
}
4.2 跨平台兼容性问题
- .NET Core/.NET 5+中的变化:
PerformanceCounter在非Windows平台不可用- 需要添加平台条件编译
csharp复制private void GetMemoryInfo()
{
#if WINDOWS
// Windows平台的PerformanceCounter实现
#else
// 其他平台的替代方案
TotalMemoryGB = GetMemoryInfoForLinuxOrMac();
#endif
}
- 替代方案:
- 对于跨平台应用,可以考虑使用
Microsoft.Diagnostics.Runtime库 - 或直接调用平台特定API
- 对于跨平台应用,可以考虑使用
4.3 性能优化建议
- 减少PerformanceCounter实例创建:
- 避免频繁创建和销毁PerformanceCounter实例
- 推荐作为类成员变量长期持有
csharp复制private readonly PerformanceCounter _committedCounter;
private readonly PerformanceCounter _availableCounter;
public SystemResourceInfo()
{
_committedCounter = new PerformanceCounter("Memory", "Committed Bytes");
_availableCounter = new PerformanceCounter("Memory", "Available Bytes");
}
- 适当调整刷新频率:
- 根据实际需求调整定时器间隔
- 一般系统监控2-5秒刷新一次足够
- 高频刷新(如<1秒)可能导致性能问题
5. 高级应用场景扩展
5.1 历史数据记录与分析
可以实现资源使用的历史记录功能,用于分析系统负载趋势:
csharp复制public class ResourceUsageHistory
{
private readonly Queue<(DateTime Time, double MemoryUsage, double StorageUsage)> _history = new();
private readonly int _maxRecords;
public ResourceUsageHistory(int maxRecords = 100)
{
_maxRecords = maxRecords;
}
public void AddRecord(double memoryUsage, double storageUsage)
{
_history.Enqueue((DateTime.Now, memoryUsage, storageUsage));
if (_history.Count > _maxRecords)
{
_history.Dequeue();
}
}
public IEnumerable<(DateTime Time, double MemoryUsage, double StorageUsage)> GetHistory()
{
return _history.ToArray();
}
}
5.2 资源预警机制
可以添加阈值预警功能,当资源使用超过设定值时触发通知:
csharp复制public class ResourceMonitor
{
public event EventHandler<ResourceThresholdExceededEventArgs> ThresholdExceeded;
private readonly double _memoryThreshold;
private readonly double _storageThreshold;
public ResourceMonitor(double memoryThreshold = 0.8, double storageThreshold = 0.9)
{
_memoryThreshold = memoryThreshold;
_storageThreshold = storageThreshold;
}
public void CheckThresholds(SystemResourceInfo info)
{
double memoryUsage = info.UsedMemoryGB / info.TotalMemoryGB;
double storageUsage = info.UsedStorageGB / info.TotalStorageGB;
if (memoryUsage >= _memoryThreshold)
{
ThresholdExceeded?.Invoke(this,
new ResourceThresholdExceededEventArgs(
ResourceType.Memory,
memoryUsage));
}
if (storageUsage >= _storageThreshold)
{
ThresholdExceeded?.Invoke(this,
new ResourceThresholdExceededEventArgs(
ResourceType.Storage,
storageUsage));
}
}
}
public enum ResourceType { Memory, Storage }
public class ResourceThresholdExceededEventArgs : EventArgs
{
public ResourceType ResourceType { get; }
public double UsagePercentage { get; }
public ResourceThresholdExceededEventArgs(ResourceType type, double percentage)
{
ResourceType = type;
UsagePercentage = percentage;
}
}
5.3 可视化图表展示
结合LiveCharts等图表库,可以实现资源使用的可视化展示:
csharp复制public class ResourceChartViewModel
{
public SeriesCollection MemorySeries { get; }
public SeriesCollection StorageSeries { get; }
public ResourceChartViewModel()
{
MemorySeries = new SeriesCollection
{
new LineSeries
{
Title = "内存使用",
Values = new ChartValues<double>()
}
};
StorageSeries = new SeriesCollection
{
new LineSeries
{
Title = "存储使用",
Values = new ChartValues<double>()
}
};
}
public void AddDataPoint(double memoryUsage, double storageUsage)
{
MemorySeries[0].Values.Add(memoryUsage);
StorageSeries[0].Values.Add(storageUsage);
// 限制数据点数量
if (MemorySeries[0].Values.Count > 50)
{
MemorySeries[0].Values.RemoveAt(0);
StorageSeries[0].Values.RemoveAt(0);
}
}
}
在实际项目中,我发现合理设置刷新间隔对平衡性能和准确性至关重要。对于大多数系统监控场景,2-3秒的刷新频率既能提供足够实时性的数据,又不会对系统性能造成明显影响。当需要更精确的数据时,可以考虑在第一次获取值后添加短暂延迟,然后再次读取,这样能显著提高PerformanceCounter数据的准确性。