Penggunaan Aplikasi. Lakukan ()

272

Apakah Application.DoEvents()bisa digunakan dalam C #?

Apakah fungsi ini merupakan cara untuk memungkinkan GUI untuk mengejar ketinggalan dengan sisa aplikasi, sama seperti VB6 DoEvents?

Craig Johnston
sumber
35
DoEventsadalah bagian dari Formulir Windows, bukan bahasa C #. Dengan demikian, dapat digunakan dari bahasa .NET. Namun, itu tidak boleh digunakan dari bahasa .NET.
Stephen Cleary
3
Ini tahun 2017. Gunakan Async / Menunggu. Lihat akhir jawaban @hansPassant untuk detailnya.
FastAl

Jawaban:

468

Hmya, mistik abadi DoEvents (). Sudah ada banyak reaksi terhadapnya, tetapi tidak ada yang benar-benar menjelaskan mengapa itu "buruk". Jenis kebijaksanaan yang sama dengan "jangan bermutasi sebuah struct". Erm, mengapa runtime dan bahasa mendukung mutating struct jika itu sangat buruk? Alasan yang sama: Anda menembak diri sendiri di kaki jika Anda tidak melakukannya dengan benar. Dengan mudah. Dan melakukannya dengan benar membutuhkan mengetahui persis apa yang dilakukannya, yang dalam kasus DoEvents () jelas tidak mudah untuk diraih.

Langsung saja: hampir semua program Windows Forms sebenarnya berisi panggilan ke DoEvents (). Ini disamarkan dengan cerdik, namun dengan nama yang berbeda: ShowDialog (). DoEvents () yang memungkinkan dialog menjadi modal tanpa membekukan sisa jendela dalam aplikasi.

Sebagian besar programmer ingin menggunakan DoEvents untuk menghentikan antarmuka pengguna agar tidak membeku ketika mereka menulis loop modal mereka sendiri. Tentu saja itu; itu mengirim pesan Windows dan mendapatkan permintaan cat dikirim. Namun masalahnya adalah tidak selektif. Ini tidak hanya mengirim pesan cat, tapi juga mengirim yang lainnya.

Dan ada satu set pemberitahuan yang menyebabkan masalah. Mereka datang sekitar 3 kaki di depan monitor. Misalnya pengguna dapat menutup jendela utama saat loop yang memanggil DoEvents () sedang berjalan. Itu berhasil, antarmuka pengguna hilang. Tetapi kode Anda tidak berhenti, itu masih mengeksekusi loop. Itu buruk. Sangat sangat buruk.

Masih ada lagi: Pengguna dapat mengklik item menu yang sama atau tombol yang menyebabkan loop yang sama untuk memulai. Sekarang Anda memiliki dua loop bersarang yang mengeksekusi DoEvents (), loop sebelumnya ditangguhkan dan loop baru mulai dari awal. Itu bisa berhasil, tetapi kemungkinannya kecil. Terutama ketika loop bersarang berakhir dan yang ditangguhkan dilanjutkan, mencoba untuk menyelesaikan pekerjaan yang sudah selesai. Jika itu tidak mengebom dengan pengecualian maka pasti data tersebut diacak semua ke neraka.

Kembali ke ShowDialog (). Itu mengeksekusi DoEvents (), tetapi perhatikan bahwa ia melakukan sesuatu yang lain. Ini menonaktifkan semua jendela dalam aplikasi , selain dialog. Sekarang masalah 3 kaki diselesaikan, pengguna tidak dapat melakukan apa pun untuk mengacaukan logika. Mode kegagalan tutup-jendela dan mulai-pekerjaan-lagi terpecahkan. Atau dengan kata lain, tidak ada cara bagi pengguna untuk membuat kode program Anda dalam urutan yang berbeda. Itu akan dieksekusi dapat diprediksi, sama seperti itu ketika Anda menguji kode Anda. Itu membuat dialog sangat menjengkelkan; siapa yang tidak suka dialog aktif dan tidak bisa menyalin dan menempelkan sesuatu dari jendela lain? Tapi itu harganya.

Yang diperlukan untuk menggunakan DoEvents dengan aman dalam kode Anda. Menyetel properti Diaktifkan dari semua formulir Anda ke false adalah cara cepat dan efisien untuk menghindari masalah. Tentu saja, tidak ada programmer yang benar-benar suka melakukan ini. Dan tidak. Itu sebabnya Anda tidak harus menggunakan DoEvents (). Anda harus menggunakan utas. Meskipun mereka memberi Anda persenjataan lengkap cara untuk menembak kaki Anda dengan cara yang penuh warna dan tidak bisa dipahami. Tetapi dengan keuntungan bahwa Anda hanya menembak kaki Anda sendiri; itu tidak akan (biasanya) membiarkan pengguna menembak miliknya.

Versi C # dan VB.NET selanjutnya akan memberikan senjata yang berbeda dengan kata kunci tunggu dan async yang baru. Terinspirasi sebagian kecil oleh masalah yang disebabkan oleh DoEvents dan utas tetapi sebagian besar oleh desain API WinRT yang mengharuskan Anda untuk menjaga UI Anda diperbarui saat operasi asinkron berlangsung. Suka membaca dari file.

Hans Passant
sumber
1
Ini hanya ujung dari es. Saya telah menggunakan Application.DoEventstanpa masalah sampai saya menambahkan beberapa fitur UDP ke kode saya, yang menghasilkan masalah yang dijelaskan di sini . Aku ingin tahu apakah ada cara sekitar yang dengan DoEvents.
darda
ini adalah jawaban yang bagus, satu-satunya hal yang saya rindukan adalah penjelasan / diskusi tentang pelaksanaan eksekusi dan desain thread-safe atau penggalian waktu secara umum - tetapi itu dengan mudah tiga kali lebih banyak teks lagi. :)
jheriko
5
Akan mendapat manfaat dari solusi yang lebih praktis daripada umumnya "menggunakan utas". Misalnya, BackgroundWorkerkomponen mengelola utas untuk Anda, menghindari sebagian besar hasil warna-warni dari pemotretan kaki, dan itu tidak memerlukan versi bahasa C # yang berdarah.
Ben Voigt
29

Bisa saja, tapi ini peretasan.

Lihat Apakah DoEvents Evil? .

Langsung dari halaman MSDN yang thedev dirujuk:

Memanggil metode ini menyebabkan utas saat ini ditangguhkan sementara semua pesan jendela tunggu diproses. Jika pesan menyebabkan suatu peristiwa dipicu, maka area lain dari kode aplikasi Anda dapat dieksekusi. Ini dapat menyebabkan aplikasi Anda menunjukkan perilaku tak terduga yang sulit di-debug. Jika Anda melakukan operasi atau perhitungan yang membutuhkan waktu lama, sering kali lebih baik melakukan operasi tersebut pada utas baru. Untuk informasi lebih lanjut tentang pemrograman asinkron, lihat Ikhtisar Pemrograman Asinkron.

Jadi Microsoft memperingatkan agar tidak menggunakannya.

Juga, saya menganggapnya sebagai peretasan karena perilakunya tidak dapat diprediksi dan rawan efek samping (ini berasal dari pengalaman mencoba menggunakan DoEvents alih-alih memutar utas baru atau menggunakan pekerja latar belakang).

Tidak ada machismo di sini - jika itu berfungsi sebagai solusi yang kuat saya akan mengatasinya. Namun, mencoba menggunakan DoEvents di .NET telah menyebabkan saya tidak merasa sakit.

RQDQ
sumber
1
Perlu dicatat bahwa pos itu berasal dari 2004, sebelum .NET 2.0 dan BackgroundWorkermembantu menyederhanakan cara yang "benar".
Justin
Sepakat. Juga, pustaka tugas di .NET 4.0 juga cukup bagus.
RQDQ
1
Mengapa ini menjadi peretasan jika Microsoft telah menyediakan fungsi untuk tujuan yang sering dibutuhkannya?
Craig Johnston
1
@Craig Johnston - memperbarui jawaban saya ke detail yang lebih lengkap mengapa saya percaya DoEvents termasuk dalam kategori peretasan.
RQDQ
Artikel koding horor itu menyebutnya sebagai "DoEvents spackle." Cemerlang!
gonzobrains
24

Ya, ada metode DoEvents statis di kelas Aplikasi di namespace System.Windows.Forms. System.Windows.Forms.Application.DoEvents () dapat digunakan untuk memproses pesan yang menunggu dalam antrian pada utas UI saat melakukan tugas yang sudah berjalan lama pada utas UI. Ini memiliki manfaat membuat UI tampak lebih responsif dan tidak "terkunci" saat tugas panjang sedang berjalan. Namun, ini hampir selalu BUKAN cara terbaik untuk melakukan sesuatu. Menurut Microsoft yang memanggil DoEvents "... menyebabkan utas saat ini ditangguhkan sementara semua pesan jendela tunggu diproses." Jika suatu peristiwa dipicu ada potensi bug yang tak terduga dan intermiten yang sulit dilacak. Jika Anda memiliki tugas yang luas, jauh lebih baik melakukannya di utas terpisah. Menjalankan tugas panjang dalam utas terpisah memungkinkannya diproses tanpa mengganggu UI yang terus berjalan dengan lancar. Lihatdi sini untuk lebih jelasnya.

Berikut adalah contoh cara menggunakan DoEvents; perhatikan bahwa Microsoft juga memberikan peringatan untuk tidak menggunakannya.

Bill W
sumber
13

Dari pengalaman saya, saya akan menyarankan sangat berhati-hati dengan menggunakan DoEvents di .NET. Saya mengalami beberapa hasil yang sangat aneh ketika menggunakan DoEvents di TabControl yang berisi DataGridViews. Di sisi lain, jika semua yang Anda hadapi adalah formulir kecil dengan bilah kemajuan maka mungkin tidak apa-apa.

Intinya adalah: jika Anda akan menggunakan DoEvents, maka Anda perlu mengujinya secara menyeluruh sebelum menggunakan aplikasi Anda.

Craig Johnston
sumber
Jawaban yang bagus, tetapi jika saya dapat menyarankan solusi untuk hal progressbar yang menunggu lebih baik , saya akan mengatakan, lakukan pekerjaan Anda di utas terpisah, buat indikator kemajuan Anda tersedia dalam variabel yang tidak stabil dan saling terkait, dan segarkan progressbar Anda dari pengatur waktu . Dengan cara ini, tidak ada pembuat kode pemeliharaan yang tergoda untuk menambahkan kode kelas berat ke loop Anda.
mg30rg
1
DoEvents atau yang setara tidak dapat dihindari jika Anda memiliki proses UI yang kuat dan tidak ingin mengunci UI. Opsi pertama adalah untuk tidak melakukan pemrosesan UI dalam jumlah besar, tetapi hal ini dapat membuat kode tersebut kurang dapat dipelihara. Namun, tunggu Dispatcher.Yield () melakukan hal yang sangat mirip dengan DoEvents dan pada dasarnya dapat memungkinkan proses UI yang mengunci layar untuk dibuat untuk semua maksud dan tujuan async.
Pengembang Melbourne
11

Iya.

Namun, jika Anda perlu menggunakan Application.DoEvents, ini sebagian besar merupakan indikasi desain aplikasi yang buruk. Mungkin Anda ingin melakukan pekerjaan di utas terpisah sebagai gantinya?

Frederik Gheysels
sumber
3
bagaimana jika Anda menginginkannya sehingga Anda dapat berputar dan menunggu pekerjaan untuk menyelesaikan di utas lainnya?
jheriko
@ jheriko Maka Anda benar-benar harus mencoba async-tunggu.
mg30rg
5

Saya melihat komentar jheriko di atas dan pada awalnya setuju bahwa saya tidak dapat menemukan cara untuk menghindari penggunaan DoEvents jika Anda akhirnya memutar utas UI utama Anda menunggu sepotong kode asinkron yang berjalan lama pada utas lain untuk diselesaikan. Tetapi dari jawaban Matthias, Refresh sederhana dari sebuah panel kecil di UI saya dapat menggantikan DoEvents (dan menghindari efek samping yang buruk).

Lebih detail tentang kasus saya ...

Saya sedang melakukan hal berikut (seperti yang disarankan di sini ) untuk memastikan bahwa layar splash type bar kemajuan ( Cara menampilkan overlay "memuat" ... ) diperbarui selama perintah SQL yang berjalan lama:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

Yang buruk: Bagi saya menelepon DoEvents berarti bahwa klik mouse kadang-kadang menembaki formulir di belakang layar splash saya, bahkan jika saya membuatnya TopMost.

Jawabannya: ganti baris DoEvents dengan panggilan Refresh sederhana ke panel kecil di tengah layar splash saya FormSplash.Panel1.Refresh(),. UI memperbarui dengan baik dan keanehan DoEvents yang telah diperingatkan orang lain telah hilang.

Baiklah
sumber
3
Refresh tidak memperbarui jendela. Jika pengguna memilih jendela lain di desktop, mengklik kembali ke jendela Anda tidak akan berpengaruh dan OS akan mendaftar aplikasi Anda sebagai tidak responsif. DoEvents () melakukan lebih dari sekadar penyegaran, karena berinteraksi dengan OS melalui sistem pengiriman pesan.
ThunderGr
4

Saya telah melihat banyak aplikasi komersial, menggunakan "DoEvents-Hack". Terutama ketika rendering mulai berperan, saya sering melihat ini:

while(running)
{
    Render();
    Application.DoEvents();
}

Mereka semua tahu tentang kejahatan metode itu. Namun, mereka menggunakan peretasan, karena mereka tidak tahu solusi lain. Berikut adalah beberapa pendekatan yang diambil dari posting blog oleh Tom Miller :

  • Setel formulir Anda agar semua gambar muncul di WmPaint, dan lakukan rendering Anda di sana. Sebelum akhir metode OnPaint, pastikan Anda melakukan ini.Invalidate (); Ini akan menyebabkan metode OnPaint dipecat lagi dengan segera.
  • P / Panggil API Win32 dan panggil PeekMessage / TranslateMessage / DispatchMessage. (Doevents sebenarnya melakukan sesuatu yang serupa, tetapi Anda dapat melakukan ini tanpa alokasi tambahan).
  • Tulis kelas formulir Anda sendiri yang merupakan pembungkus kecil di sekitar CreateWindowEx, dan beri diri Anda kontrol penuh atas loop pesan. -Tentukan bahwa metode DoEvents berfungsi dengan baik untuk Anda dan tetap menggunakannya.
Matthias
sumber
3

Lihatlah Dokumentasi MSDN untuk Application.DoEventsmetode ini.

thev
sumber
3

DoEvents memungkinkan pengguna untuk mengklik atau mengetik dan memicu acara lainnya, dan utas latar belakang adalah pendekatan yang lebih baik.

Namun, masih ada kasus di mana Anda mungkin mengalami masalah yang membutuhkan pembilasan pesan acara. Saya mengalami masalah di mana kontrol RichTextBox mengabaikan metode ScrollToCaret () ketika kontrol memiliki pesan dalam antrian untuk diproses.

Kode berikut memblokir semua input pengguna saat menjalankan DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}
cat_in_hat
sumber
1
Anda harus selalu memblokir acara saat memanggil pelaku. Kalau tidak, acara lain di aplikasi Anda akan diaktifkan sebagai respons dan aplikasi Anda bisa mulai melakukan dua hal sekaligus.
FastAl
2

Application.DoEvents dapat membuat masalah, jika sesuatu selain pemrosesan grafis dimasukkan ke dalam antrian pesan.

Ini dapat berguna untuk memperbarui bilah kemajuan dan memberi tahu pengguna tentang kemajuan dalam sesuatu seperti konstruksi dan pemuatan MainForm, jika itu membutuhkan waktu.

Dalam aplikasi terbaru yang saya buat, saya menggunakan DoEvents untuk memperbarui beberapa label pada Layar Pemuatan setiap kali blok kode dieksekusi dalam konstruktor MainForm saya. Utas UI adalah, dalam hal ini, sibuk dengan mengirim email pada server SMTP yang tidak mendukung panggilan SendAsync (). Saya mungkin bisa membuat utas yang berbeda dengan metode Begin () dan End () dan memanggil Send () dari mereka, tetapi metode itu rawan kesalahan dan saya lebih suka Formulir Utama dari aplikasi saya tidak membuang pengecualian selama konstruksi.

Tamu123
sumber