如何避免RCW清理比赛

时间:2012-03-07 10:16:58

标签: c# .net com concurrency rcw

我有一个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是Disposable,将其ManagementObjectSearcher置于Dispose方法中 我在main方法
  • 中的Application.Run之后调用StateReader对象上的Dispose
  • 在CPULoad中,我处理了ManagementCollection和每个ManagementObject
  • 在我的main方法中,我在FormClosing上的事件处理程序中处理订阅对象 在gui上。这应该确保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();

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

2 个答案:

答案 0 :(得分:1)

我认为您需要使StateReader一次性使用并在退出申请前进行处理。 StateReader应处置searcher。但是,我认为真正的问题是您没有在ManagementObject中处置CPULoad。如果GC在CPULoad之后运行,RCW将被释放。但是,如果您在GC之前退出,则可能会触发您看到的异常。

  

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

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

答案 1 :(得分: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。