Подтвердить что ты не робот

"Ошибка создания оконной ручки"

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

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

Вы должны иметь возможность воспроизвести проблему. Вот один из способов записи шагов для этого fooobar.com/questions/88026/....

Самый простой способ выработать то, что создает столько ручек, - открыть TaskMgr.exe. В TaskMgr.exe необходимо, чтобы столбцы USER Object, GDI Object и Hands отображались как показано, для этого выберите "Меню просмотра" > "Выбрать столбцы":

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

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

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

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

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

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

Я отследил его до этого кода, используемого при установке ширины столбцов 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() at 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 width, Int32 height, BoundsSpecified) в System.Windows.Forms.Control.set_Width (значение Int32)

Вот суть полезная статья Фабриса, которая помогла мне разобраться в пределах:

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

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

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

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

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

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

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

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

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

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

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

Ответ 6

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

For k = 1 To Panel.Controls.Count
    Panel.Controls.Item(0).Dispose()
Next

Ответ 7

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

Ответ 8

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

 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

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