У меня есть приложение 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();
Есть ли что-нибудь еще, что я мог сделать, чтобы избежать этой ошибки я получаю ?
Вы поставили диагноз правильно. Это не единственная проблема, вызов .ObserveOn (gui) тоже очень хлопот. У вас есть *, чтобы убедиться, что больше не будет создано никаких уведомлений, прежде чем вы сможете закрыть форму. Таковы опасности, позволяющие потокам бегать. –
@Hans Passant: Я отредактировал свой код, чтобы избавиться от подписки на FormClosing. Не могли бы вы сказать, что это решает проблему, о которой вы упомянули, - у меня нет других событий в форме, кроме событий, происходящих от пользователя, взаимодействующего с ним, такого как нажатия кнопки и т. П. – Boris
Наверное, нет, это нить, если поток TP запланирован при вызове Dispose(), но еще не запущен. Я не очень хорошо знаю реактивный водопровод. –