【问题标题】:How to avoid race on RCW cleanup如何避免 RCW 清理比赛
【发布时间】:2012-03-07 10:16:58
【问题描述】:

我有一个 gui 应用程序,它会定期显示 CPU 负载。负载由 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();
    }
}

使用响应式扩展更新 gui:

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.

如果我不读取 cpu 负载,则不会出现此错误,而只是提供一些随机值,因此该错误以某种方式与读取负载有关。此外,如果我在Application.Run(gui) 之后放置一个断点并在那里稍等片刻,则错误似乎不会经常出现。

从这个和我的谷歌搜索来看,我认为使用管理命名空间中的类会创建一个后台线程,该线程引用包装在运行时可调用包装器中的 COM 对象,并且当我退出我的应用程序时,该线程没有时间正确关闭 RCW,导致我的错误。这是正确的,我该如何解决这个问题?


我已经编辑了我的代码以反映我得到的响应,但我仍然遇到同样的错误。代码更新了三点:

  • StateReader 是 Disposable,在 Dispose 方法中释放其 ManagementObjectSearcher 在我的 main 方法中 Application.Run 之后,我在 StateReader 对象上调用 Dispose
  • 在 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 上处理订阅。你会说这解决了你提到的问题吗?我在表单上没有其他事件,除了来自用户与其交互的事件,例如按钮点击等。
  • 可能不是,如果在调用 Dispose() 时安排了 TP 线程但尚未开始执行,则这是线程竞争。我不太了解 Reactive 管道。
  • @HansPassant:我没想到。但是想一想,这是否适用于从另一个线程调用表单上的方法的任何方案?这个问题一般有解决办法吗?
  • 当然,因此我不是 Reactive 的忠实粉丝。它也没有完全席卷世界。替代方案是优秀的样板,只需使用同步计时器。

标签: c# .net com concurrency rcw


【解决方案1】:

我认为您需要将 StateReader 设为一次性并在退出应用程序之前将其丢弃。 StateReader 应该处理 searcher。但是,我认为真正的问题是您没有在CPULoad 中处理ManagementObject。如果 GC 在CPULoad 之后运行,则 RCW 将被释放。但是,如果您在 GC 之前退出,那么这可能会触发您看到的异常。

我认为使用管理命名空间中的类会创建一个后台线程,该线程引用包装在 Runtime Callable Wrapper 中的 COM 对象

Observable.Interval 创建一个后台线程,CPULoad 在该线程上执行。

【讨论】:

  • 谢谢,我忘记了 Observable.Interval 的后台线程。我已经编辑了我的代码以反映您的建议,但我仍然收到错误。
【解决方案2】:

不要让应用程序在后台线程运行时退出CPULoad 以避免它。

我能想到的最简单的解决方案是从一个新的前台线程获取 CPU 负载,然后加入这些线程。

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 调用相比,每次启动新线程的开销可以忽略不计。

cmets 中提到的同步 Timer 实现了几乎相同的行为,但它阻塞了 UI 线程并且几乎无法用于慢 WMI。

【讨论】:

    猜你喜欢
    • 2014-08-24
    • 2019-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-23
    • 1970-01-01
    • 2011-02-26
    相关资源
    最近更新 更多