PerformanceCounter蛋痛的设计
在.NET下对进程的性能计数可以使用PerformanceCounter,通过该对象可以对进程的CPU,内存等信息进行统计.对于正常使用来说这个对象还是很方便,但对于同一名称的多个进程进行性能计数那真是可以无比蛋痛...详细说一下PerformanceCounter对多个同一名称的进程计数所面对的问题.
应用情况
PerformanceCounter是通过进程实例名来监控计数,但这个实例名是操作系统实时动态分配的,如果同一名称的进程只有一个那就比较好处理.如果同一名称的进程有多个那悲剧的事情就来的,随着同一名称的进程创建和关闭会影响到其他同一名称进程的实例名.这样会导致相应的PerformanceCounter和进程的对应关系完全错乱...(真是无法理解.NET对这个的实现为什么不用PID)
假设现在有个ams-p.exe应用程序,依次分别打开三次,那对应的进程实例名是:
1)ams-p.exe [pid:7012]
2)ams-p.exe#1 [pid:7013]
3)ams-p.exe#2 [pid:7014]
由于实例名windows系统实时分配的,一旦同一名称的进程数量发现变化那对应进程的实例名也会变.针对上面情况当2被关闭后那实例对应的关系可以是.
1)ams-p.exe [pid:7012]
3)ams-p.exe#1 [pid:7014]
这样就会导致3对应的实例名不存在,从而导致PerformanceCounter上的统计错误,如果对应同一名称的进程数量更多,中间其中一个进程关闭那就会有大量的PerformanceCounter统计和进程对应关系就都会存在错误的.
解决办法
虽然有解决方法,但做法都是非常蛋痛的,监控Process的Exite事件然后根据退出情况来调整实例名和PID的对应关系,不过这种方式是存在一些问题,如果其他应用也是这个进程名又是通过其他渠道打开你未及时捕抓绑定Exite事件,那就会导致计数错误的悲剧事件发生.而另一种解决方法就比较全面点,在执行所有PerformanceCounter前行把当前系统该名称的进程信息获取上载并建立一个实时的实例名和PID对应关系,然后在执行PerformanceCounter后记录对应实例名的PID计数情况.具体代码如下:
class CPUCounter:IDisposable { public CPUCounter(string processName) { mProcessName = processName; mTimer = new System.Threading.Timer(GetUsage, null, 1000, 1000); } private System.Threading.Timer mTimer; private string mProcessName; private Dictionary<int, float> mProcessCpuUsage = new Dictionary<int, float>(); private List<CounterItem> mCounters = new List<CounterItem>(); private Dictionary<string, int> mProcessIDs = new Dictionary<string, int>(); public float ProcessUsage(int pid) { float result = 0; mProcessCpuUsage.TryGetValue(pid, out result); return result; } private void OnCreateCounter(string processname) { CounterItem item = mCounters.Find(e => e.ProcessName == processname); if (item == null) { item = new CounterItem(); item.Counter = new PerformanceCounter(); item.Counter.CategoryName = "Processor"; item.Counter.CounterName = "% Processor Time"; item.Counter.InstanceName = processname; } item.Enabled = true; } private void GetUsage(object state) { mProcessIDs.Clear(); Process[] ps = Process.GetProcessesByName(mProcessName); List<CounterItem> disposeditems = new List<CounterItem>(); if (ps.Length == 1) { mProcessIDs.Add(mProcessName, ps[0].Id); OnCreateCounter(mProcessName); } else { for (int i = 1; i < ps.Length; i++) { mProcessIDs.Add(mProcessName + "#" + i, ps[i].Id); OnCreateCounter(mProcessName + "#" + i); } } foreach (CounterItem item in mCounters) { if (item.Enabled) { mProcessCpuUsage[mProcessIDs[item.ProcessName]] = item.Counter.NextValue(); item.Enabled = false; } else { disposeditems.Add(item); } } if(disposeditems.Count>0) foreach (CounterItem item in disposeditems) { mCounters.Remove(item); } } class CounterItem { public string ProcessName { get; set; } public System.Diagnostics.PerformanceCounter Counter { get; set; } public bool Enabled { get; set; } } public void Dispose() { if (mTimer != null) mTimer.Dispose(); } }
如果有更好办法的同学不防分享一下.