it-swarm-id.com

"Kesalahan Membuat Pegangan Jendela"

Kami sedang mengerjakan aplikasi komposit .NET WinForms yang sangat besar - bukan CAB, tetapi kerangka kerja rumah yang serupa. Kami berjalan di lingkungan Citrix dan RDP yang berjalan pada Windows Server 2003. 

Kami mulai mengalami kesalahan acak dan sulit untuk mereproduksi "Galat membuat pegangan jendela" yang tampaknya merupakan kebocoran pegangan mode lama dalam aplikasi kami. Kami banyak menggunakan kontrol Pihak ke-3 (Janus GridEX, Infralution VirtualTree, dan .NET Magic docking) dan kami melakukan banyak pemuatan dinamis dan rendering konten berdasarkan metadata di database kami.

Ada banyak info di Google tentang kesalahan ini, tetapi tidak banyak panduan yang solid tentang cara menghindari masalah di area ini.

Apakah komunitas stackoverflow memiliki panduan yang bagus untuk saya dalam membuat aplikasi winforms yang mudah digunakan?

25
user8133

Saya telah melacak banyak masalah dengan UI tidak membongkar seperti yang diharapkan di WinForms. 

Berikut ini beberapa petunjuk umum:

  • banyak waktu, kontrol akan tetap digunakan karena acara kontrol tidak dihapus dengan benar (penyedia tooltip menyebabkan kita masalah besar di sini) atau kontrol tidak dibuang dengan benar. 
  • gunakan 'menggunakan' blok di sekitar semua dialog modal untuk memastikan bahwa mereka dibuang
  • ada beberapa properti kontrol yang akan memaksa pembuatan pegangan jendela sebelum diperlukan (misalnya pengaturan properti ReadOnly dari kontrol TextBox akan memaksa kontrol direalisasikan)
  • gunakan alat seperti .Net Memory profiler untuk mendapatkan jumlah kelas yang dibuat. Versi terbaru dari alat ini juga akan melacak GDI dan objek USER.
  • cobalah untuk meminimalkan penggunaan panggilan Win API Anda (atau panggilan DllImport lainnya). Jika Anda perlu menggunakan interop, cobalah untuk membungkus panggilan ini sedemikian rupa sehingga pola penggunaan/Buang akan bekerja dengan benar.
28
Jack Bolding

Saya mengalami kesalahan ini ketika saya mensubklasifikasikan NativeWindow dan memanggil CreateHandler secara manual. Masalahnya adalah saya lupa menambahkan base.WndProc (m) dalam versi WndProc saya yang overriden. Itu menyebabkan kesalahan yang sama

6
aderesh

Saya bertemu dengan pengecualian ini karena loop tanpa akhir membuat kontrol UI baru dan mengatur propertinya. Setelah berulang kali, pengecualian ini dilemparkan ketika mengubah properti kontrol yang terlihat. Saya menemukan Objek Pengguna dan GDI Obyek (Dari Task Manager) cukup besar.

Saya kira masalah Anda adalah alasan yang sama bahwa sumber daya sistem kehabisan oleh kontrol UI tersebut.

5
sliu

Memahami kesalahan ini

Mendorong Batas Windows: USER dan GDI Objek - Bagian 1 oleh Mark Russinovich: https://blogs.technet.Microsoft.com/markrussinovich/2010/02/24/ mendorong-the-limit-of-windows-user-dan-gdi-objects-part-1/

Memecahkan masalah kesalahan ini

Anda harus dapat mereproduksi masalah. Berikut adalah salah satu cara merekam langkah-langkah untuk melakukan itu https://stackoverflow.com/a/30525957/495455 .

Cara termudah untuk mengetahui apa yang membuat begitu banyak pegangan adalah membuka TaskMgr.exe. Di TaskMgr.exe Anda harus memiliki Objek USER, GDI Obyek dan Menangani kolom terlihat seperti yang ditunjukkan, untuk melakukan ini pilih Lihat Menu> Pilih Kolom:

 enter image description here

Ikuti langkah-langkah untuk menyebabkan masalah dan saksikan peningkatan jumlah Obyek USER menjadi sekitar 10.000 atau GDI Objek atau Pegangan mencapai batasnya.

Ketika Anda melihat peningkatan Obyek atau Menangani (biasanya secara dramatis) Anda dapat menghentikan eksekusi kode di Visual Studio dengan mengklik tombol Jeda.

Kemudian tahan F10 atau F11 untuk melihat kode ketika jumlah Object/Handle meningkat secara dramatis.

Alat terbaik yang saya temukan sejauh ini adalah GDIView dari NirSoft, ini memecah GDI Menangani bidang:

 enter image description here

Saya melacaknya ke kode ini yang digunakan ketika mengatur Lebar Kolom 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

Ini adalah jejak tumpukan:

di System.Windows.Forms.Control.CreateHandle () di System.Windows.Forms.ComboBox.CreateHandle () di System.Windows.Forms.Control.get_Handle () di System.Windows.Forms.ComboBox.InvalidateEverything () di System.Windows.Forms.ComboBox.OnResize (EventArgs e) di System.Windows.Forms.Control.OnSizeChanged (EventArgs e ) di System.Windows.Forms.Control.UpdateBounds (Int32 x, Int32 y, Int32 lebar, tinggi Int32, Int32 clientWidth, Int32 clientHeight) di System.Windows. Forms.Control.UpdateBounds (Int32 x, Int32 y, Int32 Lebar, tinggi Int32) di System.Windows.Forms.Control.SetBoundsCore (Int32 x, Int32 y, Int32 lebar, tinggi Int32, BoundsSpecified ditentukan) di System.Windows.Forms.ComboBox.SetBoundsCore (Int32 x, Int32 y, Int32 Lebar, tinggi Int32, BoundsSpecified ditentukan) di [. ] System.Windows.Forms.Control.SetBounds (Int32 x, Int32 y, lebar Int32, Tinggi Int32, BoundsSpecified ditentukan) di System.Windows.Forms.Control.set_Width (nilai Int32)

Inilah inti dari artikel bermanfaat oleh Fabrice yang membantu saya mengatasi batasan:

"Kesalahan membuat pegangan jendela"
Ketika aplikasi Windows Forms besar yang saya kerjakan untuk klien digunakan secara aktif, pengguna sering mendapatkan pengecualian "Galat membuat pegangan jendela".

Terlepas dari kenyataan bahwa aplikasi tersebut menghabiskan terlalu banyak sumber daya, yang merupakan masalah terpisah yang sama sekali sudah kami bahas, kami mengalami kesulitan dalam menentukan sumber daya apa yang sedang habis serta berapa batasan untuk sumber daya ini. Kami pertama kali berpikir tentang mengawasi konter Handles di Windows Task Manager. Itu karena kami memperhatikan bahwa beberapa proses cenderung mengkonsumsi lebih banyak sumber daya ini daripada biasanya. Namun, penghitung ini bukan yang baik karena melacak sumber daya seperti file, soket, proses, dan utas. Sumber daya ini dinamai Objek Kernel.

Jenis sumber daya lain yang harus kita perhatikan adalah GDI Obyek dan Objek Pengguna. Anda bisa mendapatkan gambaran umum dari tiga kategori sumber daya di MSDN.

Objek Pengguna
Masalah pembuatan jendela terkait langsung dengan Objek Pengguna.

Kami mencoba menentukan batas dalam hal Objek Pengguna yang dapat digunakan aplikasi. Ada kuota 10.000 pegangan pengguna per proses. Nilai ini dapat diubah dalam registri, namun batas ini bukan show-stopper nyata dalam kasus kami. Batas lainnya adalah 66.536 pegangan pengguna per sesi Windows. Batas ini bersifat teoritis. Dalam praktiknya, Anda akan melihat bahwa itu tidak dapat dijangkau. Dalam kasus kami, kami mendapatkan pengecualian "Galat membuat pegangan jendela" yang ditakuti sebelum jumlah total Objek Pengguna di sesi saat ini mencapai 11.000.

Tumpukan Desktop
Kami kemudian menemukan batas mana yang merupakan penyebab sesungguhnya: itu adalah "Heap Desktop". Secara default, semua aplikasi grafis dari sesi pengguna interaktif dijalankan dalam apa yang disebut "desktop". Sumber daya yang dialokasikan untuk desktop semacam itu terbatas (tetapi dapat dikonfigurasi).

Catatan: Objek Pengguna adalah yang mengonsumsi sebagian besar ruang memori Tumpukan Desktop. Ini termasuk windows. Untuk informasi lebih lanjut tentang Heap Desktop, Anda dapat merujuk ke artikel yang sangat bagus yang diterbitkan di blog MSDN NTDebugging:

Apa solusi nyata? Jadilah hijau!
Meningkatkan Tumpukan Desktop adalah solusi yang efektif, tetapi itu bukan yang utama. Solusi sebenarnya adalah dengan mengonsumsi lebih sedikit sumber daya (lebih sedikit pegangan jendela dalam kasus kami). Saya bisa menebak betapa kecewa Anda dengan solusi ini. Apakah ini benar-benar yang dapat saya temukan ?? Ya, tidak ada rahasia besar di sini. Satu-satunya jalan keluar adalah menjadi ramping. Memiliki UI yang kurang rumit adalah awal yang baik. Ini bagus untuk sumber daya, juga baik untuk kegunaan. Langkah selanjutnya adalah menghindari pemborosan, melestarikan sumber daya, dan mendaur ulangnya!

Inilah cara kami melakukan ini di aplikasi klien saya:

Kami menggunakan TabControls dan kami membuat konten dari masing-masing tab dengan cepat, ketika itu menjadi terlihat; Kami menggunakan wilayah yang dapat diperluas/diciutkan, dan sekali lagi mengisinya dengan kontrol dan data hanya bila diperlukan; Kami lepaskan sumber daya sesegera mungkin (menggunakan metode Buang). Ketika suatu daerah runtuh, dimungkinkan untuk menghapus kontrol anak-anak itu. Hal yang sama untuk tab saat menjadi tersembunyi; Kami menggunakan pola desain MVP, yang membantu dalam membuat hal di atas mungkin karena memisahkan data dari tampilan; Kami menggunakan mesin tata letak, FlowLayoutPanel standar dan TableLayoutPanel, atau yang khusus, alih-alih membuat hierarki yang mendalam dari panel bersarang, GroupBoxes dan Splitter (splitter kosong itu sendiri menggunakan tiga pegangan jendela ...). Di atas hanyalah petunjuk tentang apa yang dapat Anda lakukan jika Anda perlu membangun layar Windows Forms yang kaya. Tidak ada keraguan bahwa Anda dapat menemukan pendekatan lain. Hal pertama yang harus Anda lakukan menurut saya adalah membangun aplikasi Anda di sekitar kasus penggunaan dan skenario. Ini membantu dalam menampilkan hanya apa yang dibutuhkan pada waktu tertentu, dan untuk pengguna tertentu.

Tentu saja, solusi lain adalah menggunakan sistem yang tidak bergantung pada pegangan ... WPF siapa pun?

3
Jeremy Thompson

Saya menggunakan Kontrol Janus di tempat kerja. Mereka sangat buggy sejauh membuangnya. Saya akan merekomendasikan agar Anda memastikan bahwa mereka dibuang dengan benar. Selain itu, pengikatan dengan mereka terkadang tidak dirilis, jadi Anda harus secara manual melepaskan ikatan objek untuk membuang kontrol.

3
MagicKat

Saya menghadapi pengecualian ini sambil menambahkan kontrol ke panel, Karena di panel kontrol anak tidak dihapus. Jika kontrol anak dibuang di panel maka bug diperbaiki.

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

Saya mengalami kesalahan .Net runtime yang sama tetapi solusi saya berbeda. 

Skenario Saya: Dari sembulan Dialog yang mengembalikan DialogResult, pengguna akan mengklik tombol untuk mengirim pesan email. Saya menambahkan utas sehingga UI tidak mengunci saat membuat laporan di latar belakang. Skenario ini akhirnya mendapatkan pesan kesalahan yang tidak biasa itu.

Kode yang menghasilkan masalah: Masalah dengan kode ini adalah bahwa utas segera dimulai dan mengembalikan yang menghasilkan DialogResult yang dikembalikan yang membuang dialog sebelum utas dapat mengambil nilai dari bidang.

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

Perbaikan untuk skenario ini: Perbaikan adalah untuk mengambil dan menyimpan nilai sebelum meneruskannya ke metode yang menciptakan utas. 

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