4

我有一个 gui 应用程序,它会定期显示 cpu 负载。负载由 StateReader 类读取:

public class StateReader
{
    ManagementObjectSearcher searcher;

    public StateReader()
    {
        ManagementScope scope = new ManagementScope("\\\\localhost\\root\\cimv2");
        ObjectQuery query = new ObjectQuery("select Name,PercentProcessorTime from Win32_PerfFormattedData_PerfOS_Processor where not Name='_Total'");
        searcher = new ManagementObjectSearcher(scope, query);
    }

    // give the maximum load over all cores
    public UInt64 CPULoad()
    {
        List<UInt64> list = new List<UInt64>();
        ManagementObjectCollection results = searcher.Get();
        foreach (ManagementObject result in results)
        {
            list.Add((UInt64)result.Properties["PercentProcessorTime"].Value); 
        }
        return list.Max();
    }
}

gui 使用响应式扩展进行更新:

var gui = new GUI();
var reader = new StateReader();

var sub = Observable.Interval(TimeSpan.FromSeconds(0.5))
                    .Select(_ => reader.CPULoad())
                    .ObserveOn(gui)
                    .Subscribe(gui.ShowCPUState);

Application.Run(gui);
sub.Dispose();

现在当我退出我的应用程序时,我收到一条错误消息

RaceOnRCWCleanup was detected. 
An attempt has been mad to free an RCW that is in use. The RCW is use on the 
active thread or another thread. Attempting to free an in-use RCW can cause 
corruption or data loss.

如果我不读取 cpu 负载,则不会出现此错误,而只是提供一些随机值,因此该错误以某种方式与读取负载有关。此外,如果我在之后放置一个断点Application.Run(gui)并在那里稍等片刻,错误似乎不会经常出现。

从这个和我的谷歌搜索来看,我认为使用管理命名空间中的类会创建一个后台线程,该线程引用包装在运行时可调用包装器中的 COM 对象,并且当我退出我的应用程序时,该线程没有时间正确关闭RCW,导致我的错误。这是正确的,我该如何解决这个问题?


我已经编辑了我的代码以反映我得到的响应,但我仍然得到同样的错误。代码更新了三点:

  • StateReader 是可处置的,在 Dispose 方法中处置其 ManagementObjectSearcher,并在我的 main 方法中 Application.Run 之后对 StateReader 对象调用 Dispose
  • 在 CPULoad 中,我处理了 ManagementCollection 和其中的每个 ManagementObject
  • 在我的主要方法中,我在 gui 上的 FormClosing
    上的事件处理程序中处理订阅对象。这应该确保关闭后不会为 gui 生成任何事件。

代码的相关部分现在位于 StateReader 中:

// give the maximum load over all cores
public UInt64 CPULoad()
{
    List<UInt64> list = new List<UInt64>();
    using (ManagementObjectCollection results = searcher.Get())
    {
        foreach (ManagementObject result in results)
        {
            list.Add((UInt64)result.Properties["PercentProcessorTime"].Value); 
            result.Dispose();
        }
    }
    return list.Max();
}

public void Dispose()
{
    searcher.Dispose();
}

而在我的主要:

gui.FormClosing += (a1, a2) => sub.Dispose();

Application.Run(gui);
reader.Dispose();

我还能做些什么来避免我得到的错误吗?

4

2 回答 2

1

我认为您需要StateReader在退出应用程序之前将其丢弃并丢弃。StateReader应该处置searcher。但是,我认为真正的问题是您没有ManagementObjectCPULoad. 如果 GC 在CPULoadRCW 之后运行,则将被释放。但是,如果您在 GC 之前退出,那么这可能会触发您看到的异常。

我认为使用管理命名空间中的类会创建一个后台线程,该线程引用包装在 Runtime Callable Wrapper 中的 COM 对象

Observable.Interval创建一个后台线程并CPULoad在该线程上执行。

于 2012-03-07T10:27:06.420 回答
0

不要让应用程序在后台线程运行时退出CPULoad以避免它。

我能想到的最简单的解决方案是从一个新的前台线程获取 CPU 负载,然后加入这些线程。

public UInt64 CPULoad()
{
    List<UInt64> list = new List<UInt64>();
    Thread thread = new Thread(() =>
    {
        ManagementObjectCollection results = searcher.Get();
        foreach (ManagementObject result in results)
        {
            list.Add((UInt64)result.Properties["PercentProcessorTime"].Value);
        }
    });
    thread.Start();
    thread.Join();
    return list.Max();
}

与慢速 WMI 调用相比,每次启动新线程的开销可以忽略不计。

评论中提到的同步 Timer 实现了几乎相同的行为,但它阻塞了 UI 线程并且几乎不能用于慢 WMI。

于 2014-10-20T16:31:15.360 回答