Как избежать гонки на очистке RCW - PullRequest
4 голосов
/ 07 марта 2012

У меня есть приложение с графическим интерфейсом, которое периодически показывает загрузку процессора.Загрузка читается классом 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();
    }
}

Графический интерфейс обновляется с использованием реактивных расширений:

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.

Эта ошибка не появляется, если я не читаю загрузку процессора, а просто предоставляю какое-то случайное значение, поэтому ошибка каким-то образом связана с чтением загрузки.Также, если я поставлю точку останова после Application.Run(gui) и подожду там немного, ошибка, похоже, будет возникать не так часто.

Исходя из этого и из моего поиска в Google, я думаю, что использование классов в пространстве имен управления создает фоновый поток, который ссылается на COM-объект, обернутый в Runtime Callable Wrapper, и когда я выхожу из приложения, этот поток неуспеть правильно закрыть RCW, что приведет к моей ошибке.Это правильно, и как я могу решить эту проблему?


Я отредактировал свой код, чтобы отразить полученные ответы, но я все еще получаю ту же ошибку.Код обновляется по трем точкам:

  • StateReader является Disposable, удаляет его ManagementObjectSearcher в методе Dispose, и я вызываю Dispose для объекта StateReader после Application.Run в моем основном методе
  • В CPULoad я располагаю ManagementCollection и каждый ManagementObject в нем
  • . В моем основном методе я располагаю объект подписки в обработчике событий FormClosing
    в графическом интерфейсе.Это должно гарантировать, что никакие события не генерируются для графического интерфейса после его закрытия.

Соответствующие части кода теперь находятся в 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 ]

1 голос
/ 07 марта 2012

Я думаю, вам нужно сделать StateReader одноразовым и утилизировать его перед выходом из приложения.StateReader должен утилизировать searcher.Тем не менее, я думаю, что реальная проблема в том, что вы не выбрасываете ManagementObject в CPULoad.Если GC запускается после CPULoad, RCW будут освобождены.Однако, если вы выйдете до GC, это может вызвать исключение, которое вы видите.

Я думаю, что использование классов в пространстве имен управления создает фоновый поток, который ссылается на COM-объект, обернутый в Runtime Callable.Оболочка

Observable.Interval создает фоновый поток, а CPULoad выполняется в этом потоке.

0 голосов
/ 20 октября 2014

Не позволяйте приложению закрываться, пока фоновый поток работает CPULoad, чтобы избежать его.

Самое простое решение, которое я могу придумать, - это получить нагрузку на процессор из нового потока переднего плана, а затемприсоединиться к потокам.

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.

Синхронный таймер, упомянутый в комментариях, обеспечивает практически то же поведение,но он блокирует поток пользовательского интерфейса и вряд ли может использоваться с медленным WMI.

...