2012-03-07 2 views
4

У меня есть приложение gui, которое периодически показывает загрузку процессора. Нагрузка считывается классом StateReader:Как избежать гонки на очистке RCW

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) и дождался там немного, ошибка, похоже, не так часто.

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


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

  • StateReader одноразовый, распоряжается его ManagementObjectSearcher в методе Dispose и я называю Dispose на объекте StateReader после Application.Run в моем основном методе
  • В CPULoad я распоряжаться ManagementCollection и каждый элемент ManagementObject в нем
  • В моем основном методе я распоряжаюсь объектом подписки в обработчике события 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(); 

Есть ли что-нибудь еще, что я мог сделать, чтобы избежать этой ошибки я получаю ?

+1

Вы поставили диагноз правильно. Это не единственная проблема, вызов .ObserveOn (gui) тоже очень хлопот. У вас есть *, чтобы убедиться, что больше не будет создано никаких уведомлений, прежде чем вы сможете закрыть форму. Таковы опасности, позволяющие потокам бегать. –

+0

@Hans Passant: Я отредактировал свой код, чтобы избавиться от подписки на FormClosing. Не могли бы вы сказать, что это решает проблему, о которой вы упомянули, - у меня нет других событий в форме, кроме событий, происходящих от пользователя, взаимодействующего с ним, такого как нажатия кнопки и т. П. – Boris

+1

Наверное, нет, это нить, если поток TP запланирован при вызове Dispose(), но еще не запущен. Я не очень хорошо знаю реактивный водопровод. –

ответ

1

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

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

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

+1

Спасибо, я забыл о фоновом потоке от Observable.Interval. Я отредактировал свой код, чтобы отразить ваши предложения, но я все равно получаю сообщение об ошибке. – Boris

0

Не позволяйте приложению выйти, пока фоновый поток работает 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.

 Смежные вопросы

  • Нет связанных вопросов^_^