it-swarm-eu.dev

"Chyba při vytváření popisovače okna"

Pracujeme na velmi rozsáhlé kompozitní aplikaci .NET WinForms - nikoliv na CAB, ale na podobném domovském rámci. Provozujeme v prostředí Citrix a RDP spuštěném v systému Windows Server 2003. 

Začínáme běžet do náhodných a obtížně reprodukovatelných chyb "Chyba při vytváření popisovače okna", která se zdá být starým módním únikem v naší aplikaci. Využíváme těžké kontroly 3. strany (Janus GridEX, Infralution VirtualTree a .NET Magic dokovací stanice) a provádíme mnoho dynamického načítání a vykreslování obsahu na základě metadat v naší databázi.

O této chybě je mnoho informací o Googlu, ale ne mnoho dobrých pokynů, jak se vyhnout problémům v této oblasti.

Má komunita stackoverflow nějaké dobré pokyny pro budování aplikací, které jsou kompatibilní s popisovačem?

25
user8133

Sledoval jsem spoustu problémů s UI není vykládání, jak se očekávalo ve WinForms. 

Zde jsou některé obecné rady:

  • hodně času, kontrola zůstane v použití protože kontroly události nejsou správně odstraněny (tooltip poskytovatel způsobil nám opravdu velké problémy tady) nebo kontroly nejsou správně Disposed. 
  • pomocí bloků „using“ kolem všech modálních dialogů zajistíte, že budou odstraněny
  • existují některé ovládací prvky, které vynutí vytvoření popisovače okna před jeho nutností (například nastavení vlastnosti ReadOnly ovládacího prvku TextBox vynutí ovládací prvek, který má být realizován)
  • použít nástroj, jako je .Net Memory profiler pro získání počtu vytvořených tříd. Novější verze tohoto nástroje budou také sledovat objekty GDI a USER.
  • pokuste se minimalizovat použití volání Win API (nebo jiných volání DllImport). Pokud potřebujete použít interop, zkuste tyto hovory zabalit tak, aby vzorek/Dispose fungoval správně.
28
Jack Bolding

Měl jsem tuto chybu, když jsem subclassed NativeWindow a volal CreateHandler ručně. Problém byl, že jsem zapomněl přidat base.WndProc (m) do mé přepsané verze WndProc. To způsobilo stejnou chybu

6
aderesh

Potkal jsem tuto výjimku, protože nekonečná smyčka vytváří nové uživatelské rozhraní a nastavuje jeho vlastnosti. Po opakovaném opakování smyčky byla tato výjimka vyvolána při změně viditelného vlastnictví. Našel jsem oba objekty uživatele a GDI Objekt (ze Správce úloh) jsou poměrně velké.

Myslím, že váš problém je podobný důvod, že systémové prostředky jsou vyčerpány těmito ovládacími prvky UI.

5
sliu

Vysvětlení této chyby

Posouvání limitů Windows: USER a GDI Objekty - část 1 Mark Russinovich: https://blogs.technet.Microsoft.com/markrussinovich/2010/02/24/ push-the-limits-of-windows-user-and-gdi-objects-part-1/

Odstraňování potíží s touto chybou

Musíte být schopni tento problém reprodukovat. Zde je jeden způsob, jak zaznamenat kroky, které je třeba udělat https://stackoverflow.com/a/30525957/495455 .

Nejjednodušší způsob, jak zjistit, co je vytváření tolik úchyty je mít TaskMgr.exe otevřít. V TaskMgr.exe musíte mít viditelný sloupec USER Object, GDI Objekty a popisovače, jak je znázorněno na obrázku, a zvolte příkaz Zobrazit menu> Vybrat sloupce:

 enter image description here

Projděte kroky, které způsobují problém a sledujte zvýšení počtu USER objektů na přibližně 10 000 nebo GDI objektů nebo úchytů dosáhnou svých limitů.

Když uvidíte zvětšení objektů nebo úchytů (typicky dramaticky), můžete zastavit spuštění kódu v aplikaci Visual Studio klepnutím na tlačítko Pozastavit.

Pak stačí podržet klávesy F10 nebo F11, aby se projížděli kódem, který pozoruje, když se počet objektů/úchytů dramaticky zvyšuje.

Nejlepší nástroj, který jsem zatím našel, je GDIView od NirSoft, rozděluje pole GDI

 enter image description here

Sledoval jsem ho až k tomuto kódu používanému při nastavování šířek sloupců 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

Toto je trasování zásobníku:

na System.Windows.Forms.Control.CreateHandle () na System.Windows.Forms.ComboBox.CreateHandle () na System.Windows.Forms.Control.get_Handle () na System.Windows.Forms.ComboBox.InvalidateEverything () na System.Windows.Forms.ComboBox.OnResize (EventArgs e) na System.Windows.Forms.Control.OnSizeChanged (EventArgs e ) na System.Windows.Forms.Control.UpdateBounds (Int32 x, Int32 y, Int32 width, Int32 výška, Int32 clientWidth, Int32 clientHeight) na System.Windows. Forms.Control.UpdateBounds (Int32 x, Int32 y, Int32 Šířka, výška Int32) na System.Windows.Forms.Control.SetBoundsCore (Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specifikováno na System.Windows.Forms.ComboBox.SetBoundsCore (Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified) na System.Windows.Forms.Control.SetBounds (Int32 x, Int32 y, Int32 šířka, Int32 výška, BoundsSpecified specifikováno) na System.Windows.Forms.Control.set_Width (Int32 hodnota)

Zde je hlavní článek užitečný článek od Fabrice který mi pomohl zjistit limity:

"Chyba při vytváření popisovače okna"
Když je aktivní velká aplikace systému Windows Forms, na které pracuji pro klienta, uživatelé často dostávají výjimky "Chyba při vytváření okna".

Kromě toho, že aplikace spotřebovává příliš mnoho zdrojů, což je zcela odlišná otázka, kterou se již zabýváme, měli jsme problémy s určováním toho, jaké zdroje byly vyčerpány a jaké jsou limity pro tyto zdroje. Nejprve jsme přemýšleli o tom, že se budeme dívat na počítadlo úchytů ve Správci úloh systému Windows. To proto, že jsme si všimli, že některé procesy měly tendenci spotřebovávat více těchto zdrojů, než by normálně měly. Tento čítač však není dobrý, protože sleduje zdroje, jako jsou soubory, zásuvky, procesy a vlákna. Tyto prostředky jsou pojmenovány objekty jádra.

Další druhy zdrojů, na které bychom měli dávat pozor, jsou GDI objekty a uživatelské objekty. Přehled tří kategorií zdrojů získáte na webu MSDN.

Objekty uživatele
Problémy při vytváření oken se přímo vztahují k uživatelským objektům.

Snažili jsme se zjistit, jaký je limit z hlediska uživatelských objektů, které může aplikace používat. Na proces je kvóta 10 000 uživatelských úchytů. Tato hodnota může být změněna v registru, avšak tento limit v našem případě nebyl skutečnou zarážkou. Druhý limit je 66 536 uživatelských úchytů na jednu relaci systému Windows. Tento limit je teoretický. V praxi si všimnete, že toho nelze dosáhnout. V našem případě jsme získali obávanou výjimku "Chyba při vytváření okna", než celkový počet objektů uživatele v aktuální relaci dosáhl 11 000.

halda plochy
Pak jsme zjistili, který limit byl skutečný viník: to byla "halda pracovní plochy". Ve výchozím nastavení se všechny grafické aplikace interaktivní uživatelské relace provádějí v tom, co se nazývá "pracovní plocha". Prostředky přidělené takovéto pracovní ploše jsou omezené (ale konfigurovatelné).

Poznámka: Objekty uživatele jsou to, co spotřebovává většinu prostoru paměti haldy plochy. To zahrnuje okna. Další informace o haldě plochy naleznete na velmi dobré články publikované na blogu MSDN NTDebugging:

Jaké je skutečné řešení? Buď zelená!
Zvýšení haldy pracovní plochy je efektivním řešením, ale to není ten konečný. Skutečným řešením je spotřebovávat méně zdrojů (v našem případě méně úchytek). Dokážu odhadnout, jak jste s tímto řešením zklamáni. Je to opravdu všechno, co můžu přijít s? No, tady není žádné velké tajemství. Jediná cesta ven je být štíhlá. Mít méně komplikované uživatelské rozhraní je dobrý začátek. Je to dobré pro zdroje, je to dobré i pro použitelnost. Dalším krokem je vyhnout se odpadům, zachovat zdroje a recyklovat je!

Zde je postup, jak to děláme v aplikaci klienta:

Používáme TabControls a vytváříme obsah každé karty za běhu, když je viditelný, Používáme rozšiřitelné/skládací oblasti a znovu je vyplňujeme ovládacími prvky a daty pouze v případě potřeby; uvolnit zdroje co nejdříve (metodou Dispose). Když se region zhroutí, je možné vyčistit jeho dětské ovládací prvky. Totéž platí pro kartu, když se skryje, Používáme návrhový vzor MVP, který pomáhá při provádění výše uvedených možností, protože odděluje data od zobrazení; Používáme rozváděče, standardní FlowLayoutPanel a TableLayoutPanel ty, nebo ty vlastní, namísto vytváření hlubokých hierarchií vnořených panelů, GroupBoxů a rozdělovačů (prázdný rozdělovač sám spotřebovává tři úchyty okna ...). Výše ​​uvedené jsou jen tipy na to, co můžete udělat, pokud jste potřebovat vytvářet bohaté obrazovky Windows Forms. Není pochyb o tom, že můžete najít další přístupy. První věc, kterou byste měli udělat podle mého názoru, je budování aplikací v případech použití a scénářů. To pomáhá zobrazovat pouze to, co je v daném čase potřebné, a pro daného uživatele.

Samozřejmě, dalším řešením by bylo použití systému, který nespoléhá na rukojeti ... WPF někdo?

3
Jeremy Thompson

Používám Janus Controls v práci. Jsou velmi buggy, pokud jde o likvidaci sebe sama. Doporučil bych, abyste se ujistili, že jsou likvidovány správně. Rovněž vazba s nimi někdy neuvolní, takže musíte ručně odblokovat objekt, který chcete ovládat.

3
MagicKat

Čelil jsem této výjimce a přidal ovládací prvky do panelu, protože v panelu kontroly dětí nebyly vymazány. Pokud zlikvidujete podřízené ovládací prvky v panelu, opravte chybu.

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

Běžel jsem do stejné .Net runtime chyby, ale moje řešení bylo jiné. 

Můj scénář: Z vyskakovacího dialogu, který vrátil dialog DialogResult, by uživatel kliknul na tlačítko a odešl e-mailovou zprávu. Přidal jsem vlákno, takže UI nezablokovalo při generování sestavy v pozadí. Tento scénář skončil tak neobvyklou chybovou zprávou.

Kód, který vyústil v problém: Problém s tímto kódem je, že podproces okamžitě spustí a vrátí, což má za následek vrácení DialogResult, který odstraní dialog před tím, než vlákno může správně zachytit hodnoty z pole.

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

Oprava tohoto scénáře: Oprava je uchopení a uložení hodnot před jejich předáním do metody, která vytváří vlákno. 

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