【问题标题】:Cannot create more Dispatcher. Runs out of resource?无法创建更多调度程序。资源耗尽?
【发布时间】:2020-08-16 13:43:10
【问题描述】:

在我们的应用程序中,我们使用 PngBitmapEncoder 在单独的线程\任务中编码和保存 PNG 图像。运行应用程序几天后,我们看到 Dispatcher 无法从 Encoder 创建并抛出错误

没有足够的存储空间来处理命令

并且有下面的调用栈

System.ComponentModel.Win32Exception (0x80004005): Not enough storage is available to process this command
   at MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook[] hooks)
   at System.Windows.Threading.Dispatcher..ctor()
   at System.Windows.Threading.DispatcherObject..ctor()
   at System.Windows.Media.Imaging.BitmapEncoder..ctor(Boolean isBuiltIn)

由于 .Net 是开源的,很好奇 Dispatcher 构造函数中的哪一行抛出错误

    [SecurityCritical, SecurityTreatAsSafe]
    private Dispatcher()
    {
        _queue = new PriorityQueue<DispatcherOperation>();

        _tlsDispatcher = this; // use TLS for ownership only
        _dispatcherThread = Thread.CurrentThread;

        // Add ourselves to the map of dispatchers to threads.
        lock(_globalLock)
        {
            _dispatchers.Add(new WeakReference(this));
        }

        _unhandledExceptionEventArgs = new DispatcherUnhandledExceptionEventArgs(this);
        _exceptionFilterEventArgs = new DispatcherUnhandledExceptionFilterEventArgs(this);

        _defaultDispatcherSynchronizationContext = new DispatcherSynchronizationContext(this);

        // Create the message-only window we use to receive messages
        // that tell us to process the queue.
        MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper();
        _window = new SecurityCriticalData<MessageOnlyHwndWrapper>( window );

        _hook = new HwndWrapperHook(WndProcHook);
        _window.Value.AddHook(_hook);

        // DDVSO:447590
        // Verify that the accessibility switches are set prior to any major UI code running.
        AccessibilitySwitches.VerifySwitches(this);
    }

更新

更新了 .net 开源的构造函数代码。 dispatcher.cs 可以在这里找到https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs,078d6b27d9837a35

经过一番调查,我们发现问题发生在大约 15000 次迭代后(每次迭代都会创建一个新线程并调用 PngBitmapEncoder)。然后发现这与 Global Atom Table 限制(0x4000 或 16384)有关。更多关于 Global Atom Table 的细节在这里https://docs.microsoft.com/en-us/archive/blogs/ntdebugging/identifying-global-atom-table-leaks

每次创建的调度程序都会在全局原子表中创建一个条目,并且在线程退出时,该条目不会被清除。这会导致全局原子表中的泄漏,当它达到最大限制时,它会抛出“存储空间不足......”错误。这似乎是 Microsoft 处理 Dispatcher 的问题。即使是 PngBitmapEncoder 文档,我也没有看到任何关于 Dispatcher 处理和任何显式关闭 Dispatcher 的评论。

【问题讨论】:

  • 您展示的是 static 构造函数,它将显示为.cctor。这不是 instance 构造函数,而是抛出的。
  • 这似乎是内存泄漏还是?这是非托管代码,您必须释放已使用的内存,您愿意这样做吗?
  • Dispatcher 是为每个线程创建一个,并创建一个用于接收 Windows 消息的本机窗口。它们存储在弱引用中,因此当您不再引用它们时它们将被最终确定/处置。您必须以某种方式持有 DispatcherObject(BitmapEncoder 是 DispatcherObject),因此即使在线程死亡后它们也不会释放 Dispatcher。
  • 错误的类,错误的构造函数,是 HwndWrapper 失败了。您可能可以使用任务管理器诊断此问题,添加“用户对象”列。当您的进程达到 10000 时,节目结束,在同一桌面上运行的所有进程组合达到 65535 时。如果这是一项服务,则适用的限制要低得多。
  • @RoXTar 我们在一段时间(3 天)内没有观察到明显的内存泄漏趋势,也没有处理泄漏。这让我在这个问题上看起来很神秘

标签: .net dispatcher bitmapencoder


【解决方案1】:

几年前,我在使用 System.Windows.Media.Imaging 命名空间中的对象进行后台处理时也遇到了这个问题。我看到一位微软工程师的博客文章承认这是一个问题,但没有足够的兴趣来解决它或类似的东西。我记得希望他们能在修订框架时修复它。工程师发布了一个适合我的解决方案。

我应该提一下,我已经尝试通过使用System.Threading.ThreadPool.QueueUserWorkItem()System.Threading.Tasks.Task.Run() 在线程池中使用该解决方案,但发现该解决方案在线程池中不起作用;也许是因为线程被重用了。我能够解决问题的唯一方法是使用System.Threading.Thread 来完成这项工作。以下是关于如何绕过问题并强制释放资源的基本思路。

new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
    // Do some imaging work.

    // This asks the dispatcher associated with this thread to shut down right away.
    System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Normal);
    System.Windows.Threading.Dispatcher.Run();
})).Start();

【讨论】:

  • 欣慰地看到并非只有我一个人遇到了这个问题。关于解决方案,我有两个问题。 Q1) 我可以使用 InvokeShutdown() 来同步调用它吗? InvokeShutdown() 的成本是多少,它会花费更多时间还是阻塞一段时间? Q2) 为什么我们需要在关闭后再次运行调度程序?
  • 整个事情令人失望,他们还没有修复它,我几年前就有这个问题。这个解决方案对我来说似乎是一个黑客,因为我和你有同样的问题。我想您可以尝试 InvokeShutdown(),但如果您的流程与我的流程类似,则需要几天时间才能确定更改是否有效。工程师在博客中提到的这两行代码确实可以按原样解决问题。从这些行开始并验证它是否解决了您的问题。然后,如果您想修改这些线路并获得良好的结果,请告诉我。就像我说的那样,我修补了线程池,但它在那里不起作用。
  • 感谢分享您的结果。 BeginInvokeShutdown() 和 InvokeShutdown() 都适合我。让我通过运行数周来击败它,如果有任何进一步的有趣结果,我会更新。再次感谢
猜你喜欢
  • 2017-12-03
  • 1970-01-01
  • 2016-06-29
  • 2015-10-03
  • 2021-10-06
  • 2011-04-17
  • 2023-04-08
  • 1970-01-01
  • 2018-04-05
相关资源
最近更新 更多