4

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?

Boris
  • 5,094
  • 4
  • 45
  • 71
  • 1
    You diagnosis is correct. It is not the only problem, the .ObserveOn(gui) call is very troublesome too. You *have* to make sure no more notifications can be generated before you allow the form to close. Such are the hazards of allowing threads to run rampant. – Hans Passant Mar 07 '12 at 10:46
  • @Hans Passant: I have edited my code to dispose the subscription on FormClosing. Would you say that this solves the problem you mentioned - I have no other events on the form, other than events stemming from the user interacting with it, such as button clicks and the like. – Boris Mar 08 '12 at 10:57
  • 1
    Probably not, it is a threading race if a TP thread is scheduled when you call Dispose() but hasn't started executing yet. I don't know the Reactive plumbing well enough. – Hans Passant Mar 08 '12 at 11:32
  • 1
    @HansPassant: I hadn't thought about that. But thinking about it, wouldn't this apply to any scheme where methods on the form are called from another thread? Is there any solution to this problem in general? – Boris Mar 08 '12 at 14:03
  • 1
    Sure, I'm not a big fan of Reactive because of this. And it hasn't exactly taken the world by storm either. The alternative is excellent and boilerplate, just use a synchronous Timer. – Hans Passant Mar 08 '12 at 14:13
  • I'm not quite sure what you mean by a synchronous timer. Apart from that, would it not be sufficient to wrap the called method in `if (!this.IsDisposed)`, since `ObserveOn(gui)` should make sure it is run on the main/gui thread, and that is presumably also where the disposing takes place? – Boris Mar 08 '12 at 15:36
  • @Boris: Answering my own question: A synchronous timer is a timer that runs on the gui thread, such as a System.Windows.Forms.Timer. – Boris Mar 28 '12 at 12:10

2 Answers2

1

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.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • 1
    Thanks, I forgot about the background thread from Observable.Interval. I have edited my code to reflect your suggestions, but I still get the error. – Boris Mar 08 '12 at 10:53
0

Don't let the application exit while the background thread is running CPULoad to avoid it.

The simplest solution I can come up with is to get the CPU load from a new foreground thread and then join the threads.

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

The overhead of starting a new thread each time is negligible in comparison with slow WMI calls.

The synchronous Timer mentioned in the comments achieves virtually the same behavior, but it blocks the UI thread and is hardly usable with slow WMI.

freegoods
  • 66
  • 1
  • 1
  • 6