«Ошибка создания дескриптора окна» - PullRequest
25 голосов
/ 18 сентября 2008

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

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

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

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

Ответы [ 8 ]

28 голосов
/ 18 сентября 2008

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

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

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

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

5 голосов
/ 13 января 2012

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

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

3 голосов
/ 25 октября 2016

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

Расширение границ 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:

enter image description here

Пройдите шаги, чтобы вызвать проблему, и наблюдайте, как количество объектов USER увеличивается примерно до 10000 или GDI Objects или Handles достигают своих пределов.

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

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

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

enter image description here

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

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

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

в 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, IntW 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, над которым я работаю для клиента, пользователи часто получают исключения «Ошибка создания дескриптора окна».

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

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

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

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

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

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

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

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

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

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

3 голосов
/ 18 сентября 2008

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

2 голосов
/ 03 сентября 2016

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

For k = 1 To Panel.Controls.Count
    Panel.Controls.Item(0).Dispose()
Next
0 голосов
/ 01 мая 2019

та же ошибка произошла, когда я начал использовать многопоточность в моем приложении 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

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

0 голосов
/ 23 сентября 2016

Я столкнулся с той же ошибкой .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.
}
...