Ошибка при создании дескриптора окна

Мы работаем над очень большим составным приложением .NET WinForms - не CAB, а похожей собственной структурой. Мы работаем в среде Citrix и RDP под управлением Windows Server 2003.

Мы начинаем сталкиваться с случайной и трудно воспроизводимой ошибкой «Ошибка создания дескриптора окна», которая кажется устаревшей утечкой дескриптора в нашем приложении. Мы активно используем сторонние элементы управления (Janus GridEX, Infralution VirtualTree и .NET Magic docking), а также выполняем большую часть динамической загрузки и рендеринга контента на основе метаданных в нашей базе данных.

В Google есть много информации об этой ошибке, но не так много надежных рекомендаций о том, как избежать проблем в этой области.

Есть ли у сообщества stackoverflow какие-нибудь полезные советы по созданию приложений winforms, удобных для дескрипторов?


person user8133    schedule 18.09.2008    source источник
comment
См. Также [этот мой пост об ошибке при создании дескриптора окна] [1] и о том, как это связано с объектами USER и кучей рабочего стола. Предлагаю несколько решений. [1]: weblogs.asp.net/fmarguerie/archive/2009/08/07/   -  person Fabrice    schedule 08.08.2009


Ответы (9)


Я отследил множество проблем с пользовательскими интерфейсами, которые не выгружаются должным образом в WinForms.

Вот несколько общих советов:

  • большую часть времени элемент управления будет оставаться в использовании, потому что события элементов управления не удаляются должным образом (поставщик всплывающих подсказок вызывал здесь действительно большие проблемы) или элементы управления не удаляются должным образом.
  • используйте блоки using вокруг всех модальных диалоговых окон, чтобы убедиться, что они удалены
  • есть некоторые свойства элемента управления, которые заставят создать дескриптор окна до того, как это станет необходимым (например, установка свойства ReadOnly элемента управления TextBox приведет к тому, что элемент управления будет реализован)
  • используйте такой инструмент, как .Net Memory Profiler, чтобы получить количество созданных классов. Более новые версии этого инструмента также будут отслеживать объекты GDI и USER.
  • постарайтесь свести к минимуму использование вызовов Win API (или других вызовов DllImport). Если вам действительно нужно использовать взаимодействие, попробуйте обернуть эти вызовы таким образом, чтобы шаблон using / Dispose работал правильно.
person Jack Bolding    schedule 18.09.2008

У меня была эта ошибка, когда я создал подкласс NativeWindow и вызвал CreateHandler вручную. Проблема заключалась в том, что я забыл добавить base.WndProc (m) в мою замененную версию WndProc. Это вызвало ту же ошибку

person aderesh    schedule 10.12.2016
comment
У меня был похожий приступ забывчивости, и это решение напомнило мне вернуться и проверить. Проблема решена. Спасибо. - person Yonabart; 17.03.2017
comment
@Yonabart, рад помочь :) - person aderesh; 12.10.2018
comment
Я изменил переопределенную версию WindProc, чтобы не запускать base.WndProc (m) при загрузке, и это вызвало проблему - person SimonKravis; 30.07.2020

Понимание этой ошибки

Расширяя границы Windows: объекты USER и GDI - часть 1, автор Марк Руссинович: https://blogs.technet.microsoft.com/markrussinovich/2010/02/24/pushing-the-limits-of-windows-user-and-gdi-objects-part-1/

Устранение этой ошибки

Вы должны уметь воспроизвести проблему. Вот один из способов записать шаги для этого https://stackoverflow.com/a/30525957/495455.

Самый простой способ выяснить, что создает такое количество дескрипторов, - открыть TaskMgr.exe. В TaskMgr.exe вам необходимо, чтобы столбцы USER Object, GDI Object и Handles были видны, как показано, для этого выберите View Menu ›Select Columns:

введите описание изображения здесь

Выполните шаги, чтобы вызвать проблему, и посмотрите, как количество объектов USER увеличивается примерно до 10 000 или объекты или дескрипторы GDI достигают своих пределов.

Когда вы видите, что объект или дескрипторы увеличиваются (обычно резко), вы можете остановить выполнение кода в Visual Studio, нажав кнопку «Пауза».

Затем просто удерживайте F10 или F11, чтобы просмотреть код, наблюдая, когда количество объектов / дескрипторов резко возрастает.

Лучший инструмент, который я нашел до сих пор, - это GDIView от NirSoft, он разбивает поля GDI Handle:

введите описание изображения здесь

Я отследил это до этого кода, который использовался при настройке местоположения и ширины столбцов поля со списком DataGridViews Filter:

If Me.Controls.ContainsKey(comboName) Then
    cbo = CType(Me.Controls(comboName), ComboBox)
    With cbo
        .Location = New System.Drawing.Point(cumulativeWidth, 0)
        .Width = Me.Columns(i).Width
    End With
    'Explicitly cleaning up fixed the issue of releasing USER objects.
    cbo.Dispose()
    cbo = Nothing  
End If

В моем случае (выше) решением было явное удаление и очистка, которая устранила проблему с освобождением объектов USER.

Это трассировка стека:

в System.Windows.Forms.Control.CreateHandle () в System.Windows.Forms.ComboBox.CreateHandle () в System.Windows.Forms.Control.get_Handle () в System.Windows.Forms.ComboBox.InvalidateEverything () в System .Windows.Forms.ComboBox.OnResize (EventArgs e) в System.Windows.Forms.Control.OnSizeChanged (EventArgs e) в System.Windows.Forms.Control.UpdateBounds (Int32 x, Int32 y, ширина Int32, высота Int32, Int32 clientWidth, Int32 clientHeight) в System.Windows.Forms.Control.UpdateBounds (Int32 x, Int32 y, ширина Int32, высота Int32) в System.Windows.Forms.Control.SetBoundsCore (Int32 x, Int32 y, ширина Int32, высота Int32 , Указано BoundsSpecified) в System.Windows.Forms.ComboBox.SetBoundsCore (Int32 x, Int32 y, ширина Int32, высота Int32, указано BoundsSpecified) в System.Windows.Forms.Control.SetBounds (Int32 x, Int32 y, ширина Int32, Высота Int32, указана BoundsSpecified) в System.Windows.Forms.Control.set_Width (значение Int32)

Вот суть полезной статьи Фабриса Это помогло мне преодолеть пределы:

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

Помимо того факта, что приложение потребляет слишком много ресурсов, что является отдельной проблемой, которую мы уже решаем, у нас возникли трудности с определением того, какие ресурсы были исчерпаны, а также каковы ограничения для этих ресурсов. Сначала мы подумали о том, чтобы следить за счетчиком Handles в диспетчере задач Windows. Это произошло потому, что мы заметили, что некоторые процессы, как правило, потребляют больше этих ресурсов, чем обычно. Однако этот счетчик не самый лучший, потому что он отслеживает такие ресурсы, как файлы, сокеты, процессы и потоки. Эти ресурсы называются Объектами ядра.

Другими видами ресурсов, за которыми мы должны следить, являются объекты GDI и пользовательские объекты. Вы можете получить обзор трех категорий ресурсов на MSDN.

Пользовательские объекты
Проблемы создания окна напрямую связаны с пользовательскими объектами.

Мы попытались определить, каков лимит с точки зрения пользовательских объектов, которые может использовать приложение. Существует квота в 10 000 пользовательских дескрипторов для каждого процесса. Это значение можно изменить в реестре, однако в нашем случае это ограничение не было реальной проблемой. Другой предел - 66 536 пользовательских дескрипторов на сеанс Windows. Это теоретический предел. На практике вы заметите, что до него нельзя добраться. В нашем случае мы получали ужасную ошибку при создании исключения дескриптора окна до того, как общее количество пользовательских объектов в текущем сеансе достигло 11000.

Куча рабочего стола
Затем мы выяснили, какое ограничение было настоящей причиной: это куча рабочего стола. По умолчанию все графические приложения интерактивного сеанса пользователя выполняются на так называемом рабочем столе. Ресурсы, выделяемые такому рабочему столу, ограничены (но настраиваются).

Примечание. Пользовательские объекты занимают большую часть памяти кучи рабочего стола. Сюда входят окна. Для получения дополнительной информации о куче рабочего стола вы можете обратиться к очень хорошим статьям, опубликованным в блоге NTDebugging MSDN:

Какое реальное решение? Будьте экологичны!
Увеличение кучи рабочего стола - эффективное решение, но не окончательное. Реальное решение - потреблять меньше ресурсов (в нашем случае меньше оконных дескрипторов). Я могу догадаться, насколько вы можете быть разочарованы этим решением. Неужели это все, что я могу придумать ?? Что ж, здесь нет большого секрета. Единственный выход - быть худым. Менее сложные пользовательские интерфейсы - хорошее начало. Это хорошо для ресурсов, это хорошо для удобства использования. Следующий шаг - избегать отходов, беречь ресурсы и перерабатывать их!

Вот как мы это делаем в моем клиентском приложении:

Мы используем TabControls и создаем содержимое каждой вкладки «на лету», когда оно становится видимым; Мы используем расширяемые / сворачиваемые области и снова заполняем их элементами управления и данными только при необходимости; Освобождаем ресурсы при первой возможности (с помощью метода Dispose). Когда регион свернут, можно очистить его дочерние элементы управления. То же самое для вкладки, когда она становится скрытой; Мы используем шаблон проектирования MVP, который помогает сделать это возможным, потому что он отделяет данные от представлений; Мы используем механизмы компоновки, стандартные FlowLayoutPanel и TableLayoutPanel или пользовательские, вместо создания глубоких иерархий вложенных панелей, GroupBoxes и Splitters (пустой разделитель занимает три оконных дескриптора ...). Вышеупомянутое - лишь намеки на то, что вы можете сделать, если вам нужно создать многофункциональные экраны Windows Forms. Несомненно, можно найти и другие подходы. На мой взгляд, первое, что вам следует сделать, - это создавать свои приложения на основе вариантов использования и сценариев. Это помогает отображать только то, что необходимо в данный момент и для данного пользователя.

Конечно, другим решением было бы использовать систему, которая не полагается на дескрипторы ... Кто-нибудь WPF?

person Jeremy Thompson    schedule 25.10.2016

Я встретил это исключение, потому что бесконечный цикл создает новый элемент управления пользовательского интерфейса и устанавливает его свойства. После многократного зацикливания это исключение было сгенерировано при изменении свойства visible элемента управления. Я обнаружил, что и пользовательский объект, и объект GDI (из диспетчера задач) довольно большие.

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

person sliu    schedule 13.01.2012

Я использую Janus Controls на работе. Они крайне глючны в том, что касается утилизации самих себя. Я бы порекомендовал вам убедиться, что они утилизируются правильно. Кроме того, привязка к ним иногда не освобождается, поэтому вам нужно вручную отвязать объект, чтобы избавиться от элемента управления.

person MagicKat    schedule 18.09.2008

Я столкнулся с этим исключением при добавлении элементов управления на панель, потому что дочерние элементы управления на панели не очищены. Исправлена ошибка при удалении дочерних элементов управления на панели.

For k = 1 To Panel.Controls.Count
    Panel.Controls.Item(0).Dispose()
Next
person Sudhakar Mallu    schedule 03.09.2016
comment
Спасибо, у меня была такая же ситуация. У меня была панель с несколькими элементами управления внутри. - person CABascourt; 28.01.2019

Я столкнулся с той же ошибкой времени выполнения .Net, но мое решение было другим.

Мой сценарий. Во всплывающем диалоговом окне, возвращающем DialogResult, пользователь нажимал кнопку, чтобы отправить сообщение электронной почты. Я добавил поток, чтобы пользовательский интерфейс не блокировался при создании отчета в фоновом режиме. Этот сценарий закончился тем, что получило необычное сообщение об ошибке.

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

private void Dialog_SendEmailSummary_Button_Click(object sender, EventArgs e)
{
    SendSummaryEmail();
    DialogResult = DialogResult.OK;
}

private void SendSummaryEmail()
{
    var t = new Thread(() => SendSummaryThread(Textbox_Subject.Text, Textbox_Body.Text, Checkbox_IncludeDetails.Checked));
    t.Start();
}

private void SendSummaryThread(string subject, string comment, bool includeTestNames)
{
    // ... Create and send the email.
}

Исправление для этого сценария. Исправление заключается в том, чтобы захватить и сохранить значения перед их передачей в метод, создающий поток.

private void Dialog_SendEmailSummary_Button_Click(object sender, EventArgs e)
{
    SendSummaryEmail(Textbox_Subject.Text, Textbox_Body.Text, Checkbox_IncludeDetails.Checked);
    DialogResult = DialogResult.OK;
}

private void SendSummaryEmail(string subject, string comment, bool includeTestNames)
{
    var t = new Thread(() => SendSummaryThread(subject, comment, includeTestNames));
    t.Start();
}

private void SendSummaryThread(string subject, string comment, bool includeTestNames)
{
    // ... Create and send the email.
}
person GrayDwarf    schedule 23.09.2016

такая же ошибка возникла, когда я начал использовать потоки в своем приложении WinForm, я использовал трассировку стека, чтобы найти, что вызывает ошибку, и обнаружил, что за этим стоит компонент инфраструктуры UltraDesktopAlert, поэтому я вызвал его по-другому, и ошибка исчезла.

 this.Invoke((MethodInvoker)delegate
{
    //call your method here
});

полный код будет выглядеть так.

private void ultraButton1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() => myMethod1());
}

void myMethod1()
{
    //my logic

    this.Invoke((MethodInvoker)delegate
    {
        ultraDesktopAlert1.Show($"my message header", "my message");
    });

    //my logic
}

также мне не удалось использовать утилиту GDI, чтобы узнать, сколько дескрипторов создает мое приложение, но мое приложение (64-битное) не было доступно в его списке. другим решением было изменить значение кучи рабочего стола на SharedSection=1024,20480,768 в следующем месте HKEY

Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems

но мой был уже с такими же ценностями. у меня работал только делегат метода вызова. надеюсь, что это помогло.

person Abubakar Riaz    schedule 01.05.2019

В моем случае я переопределил WndProc(ref Message m), но не вызвал base.WndProc(ref m);

person Vapid Linus    schedule 23.09.2020