it-swarm-eu.dev

"Fehler beim Erstellen des Fensterhandles"

Wir arbeiten an einer sehr umfangreichen .NET WinForms-Verbundanwendung - nicht CAB, sondern einem ähnlichen, hausgemachten Framework. Wir arbeiten in einer Citrix- und RDP-Umgebung unter Windows Server 2003. 

Wir beginnen mit zufälligen und schwer zu reproduzierenden Fehlern "Fehler beim Erstellen des Fensterhandles", die ein altmodisches Handle-Leck in unserer Anwendung zu sein scheinen. Wir nutzen intensiv die Steuerelemente von Drittanbietern (Janus GridEX, Infralution VirtualTree und .NET Magic-Docking) und führen viel dynamisches Laden und Rendern von Inhalten basierend auf Metadaten in unserer Datenbank durch.

Es gibt viele Informationen zu diesem Fehler bei Google, aber keine solide Anleitung, wie Probleme in diesem Bereich vermieden werden können.

Hat die stackoverflow-Community eine gute Anleitung für das Erstellen grifffreundlicher winforms-Apps?

25
user8133

Ich habe eine Menge Probleme mit UIs ermittelt, die nicht wie erwartet in WinForms entladen wurden. 

Hier einige allgemeine Hinweise:

  • in den meisten Fällen wird ein Steuerelement weiterhin verwendet, da Steuerelementereignisse nicht ordnungsgemäß entfernt werden (der Tooltip-Anbieter hat uns hier wirklich große Probleme verursacht) oder die Steuerelemente nicht ordnungsgemäß entsorgt werden. 
  • verwenden Sie Blöcke, die alle modalen Dialogfelder verwenden, um sicherzustellen, dass sie entsorgt werden
  • es gibt einige Steuerelementeigenschaften, die die Erstellung des Fensterziehpunkts erzwingen, bevor es erforderlich ist (wenn Sie beispielsweise die ReadOnly-Eigenschaft eines TextBox-Steuerelements festlegen, wird das Steuerelement erzwungen).
  • verwenden Sie ein Tool wie .Net Memory Profiler , um die Anzahl der erstellten Klassen zu ermitteln. Neuere Versionen dieses Tools verfolgen auch GDI- und USER-Objekte.
  • versuchen Sie, die Verwendung von Win-API-Aufrufen (oder anderen DllImport-Aufrufen) zu minimieren. Wenn Sie Interop verwenden müssen, versuchen Sie, diese Aufrufe so umzuwandeln, dass das Muster "/ Dispose" ordnungsgemäß funktioniert.
28
Jack Bolding

Ich hatte diesen Fehler, als ich NativeWindow untergeordnet habe und CreateHandler manuell aufgerufen habe. Das Problem war, dass ich vergessen habe, base.WndProc (m) in meiner überschriebenen Version von WndProc hinzuzufügen. Es hat den gleichen Fehler verursacht

6
aderesh

Ich traf diese Ausnahmebedingung, da die Endlosschleife ein neues UI-Steuerelement erstellt und dessen Eigenschaften festgelegt hat. Nach vielen Schleifen wurde diese Exoption ausgelöst, als die sichtbare Eigenschaft des Steuerelements geändert wurde. Ich habe sowohl User Object als auch GDI Das Objekt (vom Task-Manager) ist recht groß.

Ich denke, Ihr Problem ist ein ähnlicher Grund dafür, dass die Systemressourcen durch diese Steuerelemente der Benutzeroberfläche erschöpft sind.

5
sliu

Diesen Fehler verstehen

Die Grenzen von Windows ausloten: USER und GDI Objekte - Teil 1 von Mark Russinovich: Https://blogs.technet.Microsoft.com/markrussinovich/2010/02/24/ Schieben der Grenzen von Windows-Benutzer-und-GDI-Objekten-Teil-1/

Fehlerbehebung für diesen Fehler

Sie müssen das Problem reproduzieren können. Hier ist eine Möglichkeit, die Schritte aufzuzeichnen, mit denen https://stackoverflow.com/a/30525957/495455 ausgeführt wird.

Der einfachste Weg, um herauszufinden, was so viele Handles erzeugt, ist das Öffnen von TaskMgr.exe. In TaskMgr.exe müssen Sie die Spalten USER Object, GDI Object and Handles wie gezeigt anzeigen. Wählen Sie dazu Ansicht Menü> Spalten auswählen:

enter image description here

Führen Sie die Schritte durch, um das Problem zu verursachen, und beobachten Sie, wie die Anzahl der USER-Objekte auf etwa 10.000 ansteigt oder GDI Objekte oder Handles an ihre Grenzen stoßen.

Wenn Sie sehen, dass das Objekt oder die Ziehpunkte (normalerweise dramatisch) zunehmen, können Sie die Codeausführung in Visual Studio anhalten, indem Sie auf die Schaltfläche Anhalten klicken.

Halten Sie dann einfach die Taste F10 oder F11 gedrückt, um durch den Code zu blättern und zu beobachten, wann die Anzahl der Objekte/des Handles dramatisch ansteigt.

Das beste Werkzeug, das ich bisher gefunden habe, ist GDIView von NirSoft. Es zerlegt die GDI Handle-Felder:

enter image description here

Ich habe es mit diesem Code aufgespürt, der beim Festlegen von DataGridViews-Spaltenbreiten verwendet wurde:

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

Dies ist der Stack-Trace:

at System.Windows.Forms.Control.CreateHandle () um System.Windows.Forms.ComboBox.CreateHandle () um System.Windows.Forms.Control.get_Handle () um [.____. .] System.Windows.Forms.ComboBox.InvalidateEverything () um System.Windows.Forms.ComboBox.OnResize (EventArgs e) um System.Windows.Forms.Control.OnSizeChanged (EventArgs e ) um System.Windows.Forms.Control.UpdateBounds (Int32 x, Int32 y, Int32 Breite, Int32-Höhe, Int32 clientWidth, Int32 clientHeight) um System.Windows. Forms.Control.UpdateBounds (Int32 x, Int32 y, Int32 Breite, Int32-Höhe) um System.Windows.Forms.Control.SetBoundsCore (Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified angegeben) bei System.Windows.Forms.ComboBox.SetBoundsCore (Int32 x, Int32 y, Int32 Breite, Int32 height, BoundsSpecified angegeben) um System.Windows.Forms.Control.SetBounds (Int32 x, Int32 y, Int32-Breite, Int32-Höhe, BoundsSpecified angegeben) um System.Windows.Forms.Control.set_Width (Int32-Wert)

Hier ist der Kern von ein hilfreicher Artikel von Fabrice , der mir dabei geholfen hat, die Grenzen auszuloten:

"Fehler beim Erstellen des Fensterhandles"
Wenn eine große Windows Forms-Anwendung, an der ich für einen Client arbeite, aktiv verwendet wird, erhalten Benutzer häufig Ausnahmen "Fehler beim Erstellen von Fensterhandles".

Abgesehen von der Tatsache, dass die Anwendung zu viele Ressourcen verbraucht, was ein ganz anderes Thema ist, das wir bereits ansprechen, hatten wir Schwierigkeiten zu bestimmen, welche Ressourcen erschöpft waren und welche Grenzen diese Ressourcen haben. Wir haben zuerst darüber nachgedacht, den Handles-Zähler im Windows Task-Manager im Auge zu behalten. Das lag daran, dass wir bemerkten, dass einige Prozesse dazu neigten, mehr von diesen Ressourcen zu verbrauchen als sie normalerweise sollten. Dieser Zähler ist jedoch nicht der richtige, da er Ressourcen wie Dateien, Sockets, Prozesse und Threads verfolgt. Diese Ressourcen werden Kernelobjekte genannt.

Die anderen Arten von Ressourcen, die wir im Auge behalten sollten, sind die GDI -Objekte und die Benutzerobjekte. Sie können einen Überblick über die drei Kategorien von Ressourcen in MSDN erhalten.

Benutzerobjekte
Probleme bei der Fenstererstellung beziehen sich direkt auf Benutzerobjekte.

Wir haben versucht herauszufinden, was das Limit in Bezug auf Benutzerobjekte ist, die eine Anwendung verwenden kann. Es gibt eine Quote von 10.000 Benutzerhandles pro Prozess. Dieser Wert kann in der Registrierung geändert werden. In diesem Fall war dieser Grenzwert jedoch nicht der tatsächliche Show-Stopper. Der andere Grenzwert beträgt 66.536 Benutzerhandles pro Windows-Sitzung. Diese Grenze ist theoretisch. In der Praxis werden Sie feststellen, dass es nicht erreichbar ist. In unserem Fall haben wir die gefürchtete Ausnahme "Fehler beim Erstellen des Fensterhandles" erhalten, bevor die Gesamtzahl der Benutzerobjekte in der aktuellen Sitzung 11.000 erreichte.

Desktop-Heap
Dann haben wir herausgefunden, welches Limit der eigentliche Schuldige war: Es war der "Desktop-Heap". Standardmäßig werden alle grafischen Anwendungen einer interaktiven Benutzersitzung in einem so genannten "Desktop" ausgeführt. Die Ressourcen, die einem solchen Desktop zugewiesen werden, sind begrenzt (aber konfigurierbar).

Hinweis: Benutzerobjekte beanspruchen den größten Speicherplatz auf dem Desktop-Heap. Dies schließt Windows ein. Weitere Informationen zum Desktop-Heap finden Sie in den sehr guten Artikeln, die im NTDebugging-MSDN-Blog veröffentlicht sind:

Was ist die wirkliche Lösung? Grün sein!
Das Erhöhen des Desktop-Heap ist eine effektive Lösung, die jedoch nicht die ultimative ist. Die wirkliche Lösung besteht darin, weniger Ressourcen zu verbrauchen (in unserem Fall weniger Fensterhandles). Ich kann mir vorstellen, wie enttäuscht Sie mit dieser Lösung sein können. Ist das wirklich alles, worauf ich mich einlassen kann? Nun, hier gibt es kein großes Geheimnis. Der einzige Ausweg ist schlank zu sein. Mit weniger komplizierten UIs ist ein guter Anfang. Es ist gut für die Ressourcen, es ist auch gut für die Benutzerfreundlichkeit. Der nächste Schritt ist, Abfall zu vermeiden, Ressourcen zu schonen und zu recyceln!

So machen wir das in der Anwendung meines Kunden:

Wir verwenden TabControls und erstellen den Inhalt der einzelnen Registerkarten im laufenden Betrieb, sobald sie sichtbar werden. Wir verwenden erweiterbare/reduzierbare Bereiche und füllen sie nur bei Bedarf mit Steuerelementen und Daten. Wir Geben Sie Ressourcen so schnell wie möglich frei (mithilfe der Dispose-Methode). Wenn eine Region minimiert ist, können die untergeordneten Steuerelemente gelöscht werden. Das Gleiche gilt für eine Registerkarte, wenn sie ausgeblendet wird: Wir verwenden das MVP-Entwurfsmuster, das hilft, das Obige zu ermöglichen, da es Daten von Ansichten trennt. Wir verwenden Layout-Engines, das standardmäßige FlowLayoutPanel und TableLayoutPanel-Elemente oder angepasste, anstatt tiefe Hierarchien verschachtelter Panels, GroupBoxen und Splitter zu erstellen (ein leerer Splitter selbst beansprucht drei Fenstergriffe ...). Die oben genannten Informationen sind nur Hinweise darauf, was Sie tun können, wenn Sie es tun müssen umfangreiche Windows Forms-Bildschirme erstellen. Es besteht kein Zweifel, dass Sie andere Ansätze finden können. Das Erste, was Sie meiner Meinung nach tun sollten, ist das Erstellen Ihrer Anwendungen um Anwendungsfälle und Szenarien. Dies hilft, nur das anzuzeigen, was zu einem bestimmten Zeitpunkt und für einen bestimmten Benutzer benötigt wird.

Eine andere Lösung wäre natürlich, ein System zu verwenden, das nicht auf Griffe angewiesen ist ... WPF jedermann?

3
Jeremy Thompson

Ich verwende die Janus Controls bei der Arbeit. Sie sind extrem fehlerhaft, soweit sie sich selbst entsorgen. Ich würde empfehlen, dass Sie sicherstellen, dass sie richtig entsorgt werden. Außerdem wird die Bindung mit ihnen manchmal nicht freigegeben, sodass Sie die Bindung des Objekts manuell aufheben müssen, um das Steuerelement zu entsorgen.

3
MagicKat

Ich habe diese Ausnahme beim Hinzufügen von Steuerelementen in das Bedienfeld übernommen. Wenn die untergeordneten Steuerelemente in der Leiste angeordnet sind, wurde der Fehler behoben.

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

der gleiche Fehler ist aufgetreten, als ich mit der Verwendung von Threading in meiner WinForm-App begonnen habe. Ich habe den Stack-Trace verwendet, um den Auslösefehler zu ermitteln, und festgestellt, dass die UltraDesktopAlert-Komponente von Infragistics dahintersteckt.

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

der vollständige Code sieht dann so aus.

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
}

außerdem konnte ich mit dem Dienstprogramm GDI) nicht ermitteln, wie viele Handles meine App erstellt, aber meine App (64 Bit) war nicht in der Liste enthalten. Eine andere Lösung war, den Desktop-Heap-Wert in SharedSection=1024,20480,768 An folgender Stelle HKEY

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

aber meins war schon mit gleichen werten. Nur Invoke Method Delegate arbeitete für mich. hoffe das hat geholfen.

0
Abubakar Riaz

Ich bin auf den gleichen .Net-Laufzeitfehler gestoßen, aber meine Lösung war anders. 

Mein Szenario: In einem Popup-Dialogfeld, in dem ein DialogResult zurückgegeben wurde, klickte der Benutzer auf eine Schaltfläche, um eine E-Mail-Nachricht zu senden. Ich habe einen Thread hinzugefügt, damit die Benutzeroberfläche beim Generieren des Berichts im Hintergrund nicht blockiert wurde. In diesem Szenario wurde diese ungewöhnliche Fehlermeldung angezeigt.

Der Code, der zu dem Problem geführt hat: Das Problem mit diesem Code ist, dass der Thread sofort gestartet wird und zurückgegeben wird. Dies führt dazu, dass das DialogResult zurückgegeben wird, das den Dialog zur Verfügung stellt, bevor der Thread die Werte ordnungsgemäß aus dem Felder.

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.
}

Der Fix für dieses Szenario: Der Fix besteht darin, die Werte zu packen und zu speichern, bevor sie an die Methode übergeben werden, die den Thread erstellt. 

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.
}
0
GrayDwarf