How to avoid race on RCW cleanup

Boris picture Boris · Mar 7, 2012 · Viewed 12.6k times · Source

I have a gui application, that periodically shows the cpu load. The load is read by a StateReader class:

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();
    }
}

The gui is updated using reactive extensions:

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();

Now when I exit my application, I get an error saying

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.

This error doesn't appear if I don't read the cpu load, but just supply some random value, so the error is somehow connected to reading the load. Also if I put a breakpoint after Application.Run(gui) and wait there for a bit, the error doesn't seem to come as often.

From this and from my googling I think that using the classes in the management namespace creates a background thread that references a COM object wrapped in a Runtime Callable Wrapper, and when I exit my application, that thread doesn't have time to properly close the RCW, leading to my error. Is this correct, and how can I solve this problem?


I have edited my code to reflect the responses I have got, but I still get the same error. The code is updated on three points:

  • StateReader is Disposable, disposes its ManagementObjectSearcher in the Dispose method and I call Dispose on the StateReader object after Application.Run in my main method
  • In CPULoad I dispose of the ManagementCollection and each ManagementObject in it
  • In my main method I dispose of the subscription object in an event handler on FormClosing
    on the gui. This should ensure that no events are generated for the gui after it is closed.

The relevant parts of the code are now, in 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();
}

And in my main:

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

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

Is there anything else I could do to avoid the error I get?

Answer

Martin Liversage picture Martin Liversage · Mar 7, 2012

I think you need to make StateReader disposable and dispose it before exiting your application. StateReader should dispose searcher. However, I think the real problem is that your are not disposing ManagementObject in CPULoad. If GC runs after CPULoad the RCW's will be freed. However, if you exit before GC then this might be triggering the exception you see.

I think that using the classes in the management namespace creates a background thread that references a COM object wrapped in a Runtime Callable Wrapper

Observable.Interval creates a background thread and CPULoad executes on that thread.