Application.DoEvents () kullanımı


272

Can Application.DoEvents()C # kullanılacak?

Bu işlev, GUI'nin uygulamanın geri kalanını VB6'nın yaptığı DoEventsgibi yakalamasına izin vermenin bir yolu mu?


35
DoEventsC # dilinin değil, Windows Forms'ın bir parçasıdır. Böyle olunca edebilirsiniz herhangi NET dilden kullanılabilir. Ancak, olmamalıdır herhangi NET dilden kullanılabilir.
Stephen Cleary

3
2017. Async / Await kullanın. Ayrıntılar için @hansPassant yanıtının sonuna bakın.
HızlıAl

Yanıtlar:


468

Hmya, DoEvents () kalıcı mistik. Buna karşı muazzam bir tepki vardı, ama hiç kimse bunun neden "kötü" olduğunu açıklamıyor. "Bir yapıyı değiştirmeyin" ile aynı bilgelik. Eee, çalışma zamanı ve dil neden bu kadar kötü ise bir yapıyı değiştirmeyi destekliyor? Aynı sebep: eğer doğru yapmazsan kendini ayağa vuruyorsun. Kolayca. Ve doğru yapmak, tam olarak ne yaptığını bilmeyi gerektirir , bu da DoEvents () durumunda kesinlikle grok yapmak kolay değildir.

Hemen yarasa: neredeyse tüm Windows Forms programları aslında bir DoEvents () çağrısı içerir. Zekice gizlenmiş, ancak farklı bir adla: ShowDialog (). Bir iletişim kutusunun uygulamadaki diğer pencereleri dondurmadan kalıcı olmasını sağlayan DoEvents () işlevidir.

Çoğu programcı kendi modal döngülerini yazdıklarında kullanıcı arayüzlerinin donmasını durdurmak için DoEvents kullanmak ister. Bunu kesinlikle yapar; Windows iletileri gönderir ve tüm boya isteklerini alır. Ancak sorun seçici olmamasıdır. Sadece boya mesajları göndermekle kalmaz, diğer her şeyi de sunar.

Ve soruna neden olan bir dizi bildirim var. Monitörün yaklaşık 3 feet önünde geliyorlar. Örneğin kullanıcı, DoEvents () öğesini çağıran döngü çalışırken ana pencereyi kapatabilir. Bu işe yarar, kullanıcı arayüzü gitti. Ancak kodunuz durmadı, hala döngüyü yürütüyor. Bu kötü. Çok çok kötü.

Dahası da var: Kullanıcı, aynı döngünün başlamasına neden olan aynı menü öğesini veya düğmeyi tıklatabilir. Artık DoEvents () işlemini yürüten iki iç içe döngünüz var, önceki döngü askıya alındı ​​ve yeni döngü sıfırdan başlıyor. Bu işe yarayabilir, ama çocuk oranlar zayıf. Özellikle iç içe döngü sona erdiğinde ve askıya alınmış olanı devam ettiğinde, zaten tamamlanmış bir işi bitirmeye çalışır. Bu bir istisna dışında bomba olmazsa, elbette veriler cehenneme karıştırılır.

ShowDialog () sayfasına geri dön. DoEvents () yürütür, ancak başka bir şey yaptığını unutmayın. Bu uygulamadaki tüm pencereleri kapatır iletişim dışındaki,. Artık 3 metrelik problem çözüldü, kullanıcı mantığı bozmak için hiçbir şey yapamaz. Hem pencereyi kapat hem de işi yeniden başlat hata modları çözülür. Veya başka bir deyişle, kullanıcının programınızın kodunu farklı bir sırayla çalıştırmasını sağlamanın bir yolu yoktur. Kodunuzu test ettiğinizde olduğu gibi tahmin edilebilir bir şekilde yürütülür. Diyalogları son derece can sıkıcı hale getirir; kim aktif bir diyalog kurmaktan ve başka bir pencereden bir şey kopyalayıp yapıştırmaktan nefret etmez? Ama fiyat bu.

DoEvents kodunuzda güvenle kullanmak için gereken budur. Tüm formlarınızın Enabled özelliğini false olarak ayarlamak sorunlardan kaçınmanın hızlı ve etkili bir yoludur. Tabii ki, hiçbir programcı bunu yapmayı gerçekten sevmez. Ve istemiyor. Bu yüzden DoEvents () kullanmamalısınız. Konu kullanmalısınız. Ayaklarınızı renkli ve anlaşılmaz yollarla vurmanın tam bir cephaneliğini vermelerine rağmen. Ama sadece kendi ayağını vurmanın avantajı ile; (tipik olarak) kullanıcının kendi fotoğrafını çekmesine izin vermez.

C # ve VB.NET'in sonraki sürümleri, yeni bekleme ve zaman uyumsuz anahtar kelimelerle farklı bir tabanca sağlayacaktır. DoEvents ve iş parçacıklarının neden olduğu sorundan küçük bir miktar, ancak asenkron işlem yapılırken UI'nizi güncel tutmanızı gerektiren WinRT API tasarımından ilham alındı . Bir dosyadan okumak gibi.


1
Bu buz berg'in sadece ucu. Application.DoEventsKoduma bazı UDP özellikleri eklenene kadar sorunsuz bir şekilde kullanıyorum , burada açıklanan sorunla sonuçlandı . Etrafa bir yol varsa bilmek isteriz o ile DoEvents.
darda

bu güzel bir cevap, özlediğini hissettiğim tek şey, genel olarak yürütme ve iş parçacığı için güvenli tasarım veya zaman dilimleme açıklaması / tartışmasıdır - ancak yine de üç kat daha fazla metindir. :)
jheriko

5
Genel olarak "kullanım iplikleri" ne göre daha pratik bir çözümden faydalanır. Örneğin, BackgroundWorkerbileşen sizin için konuları yönetir, ayak çekiminin renkli sonuçlarının çoğunu önler ve kanama kenarı C # dil sürümleri gerektirmez.
Ben Voigt

29

Olabilir, ama bu bir hack.

Bakınız DoEvents Evil mi? .

Doğrudan MSDN sayfasında şu thedev başvurulan:

Bu yöntemin çağrılması, bekleyen tüm pencere iletileri işlenirken geçerli iş parçacığının askıya alınmasına neden olur. Bir mesaj bir olayın tetiklenmesine neden olursa, uygulama kodunuzun diğer alanları da yürütülebilir. Bu, uygulamanızın hata ayıklaması zor olan beklenmedik davranışlar göstermesine neden olabilir. Uzun zaman alan işlemler veya hesaplamalar yaparsanız, bu işlemleri yeni bir iş parçacığında gerçekleştirmek genellikle tercih edilir. Eşzamansız programlama hakkında daha fazla bilgi için bkz. Eşzamansız Programlamaya Genel Bakış.

Bu yüzden Microsoft, kullanımına karşı uyarıyor.

Ayrıca, davranışı öngörülemeyen ve yan etkiye eğilimli olduğu için bunu bir hack olarak görüyorum (bu, yeni bir iş parçacığını döndürmek veya arka plan çalışanı kullanmak yerine DoEvents kullanmaya çalışmanın deneyiminden kaynaklanmaktadır).

Burada hiçbir machismo yok - eğer sağlam bir çözüm olarak çalışırsa her şeyin üstünde olurdum. Ancak, .NET'te DoEvents kullanmaya çalışmak bana acıdan başka bir şey yapmadı.


1
Bu yazının .NET 2.0'dan önce 2004'ten geldiğini BackgroundWorkerve "doğru" yolun basitleştirilmesine yardımcı olduğunu belirtmek gerekir .
Justin

Kabul. Ayrıca, .NET 4.0'daki Görev kitaplığı da oldukça güzel.
RQDQ

1
Microsoft, sıklıkla ihtiyaç duyulduğu amaç için bir işlev sağlamışsa neden bir hack olur?
Craig Johnston

1
@Craig Johnston - DoEvents'ın korsanlık kategorisine girdiğine inandığım cevabımı daha ayrıntılı bir şekilde güncelledi.
RQDQ

Bu kodlama korku makalesi "DoEvents spackle" olarak adlandırıldı. Parlak!
gonzobrains

24

Evet, System.Windows.Forms ad alanındaki Application sınıfında statik bir DoEvents yöntemi vardır. UI iş parçacığında uzun süre çalışan bir görev gerçekleştirirken System.Windows.Forms.Application.DoEvents (), UI iş parçacığında sırada bekleyen iletileri işlemek için kullanılabilir. Bu, uzun bir görev çalışırken kullanıcı arayüzünün daha duyarlı görünmesini ve "kilitlenmemesini" sağlar. Ancak, bu neredeyse her zaman bir şeyler yapmanın en iyi yolu DEĞİLDİR. Microsoft arama DoEvents göre "... tüm bekleyen pencere mesajları işlenirken geçerli iş parçacığı askıya neden olur." Bir olay tetiklenirse, izlenmesi zor olan beklenmedik ve aralıklı hatalar olasılığı vardır. Kapsamlı bir göreviniz varsa, ayrı bir iş parçacığında yapmak çok daha iyidir. Uzun görevleri ayrı bir iş parçacığında çalıştırmak, sorunsuz bir şekilde çalışmaya devam eden kullanıcı arayüzüne müdahale etmeden işlenmelerini sağlar. Bakdaha fazla bilgi için buraya tıklayın .

İşte DoEvents nasıl kullanılacağı ile ilgili bir örnek; Microsoft'un da kullanımına karşı bir uyarı verdiğini unutmayın.


13

Deneyimlerimden, .NET'te DoEvents kullanarak büyük dikkat gösterilmesini tavsiye ederim. DataGridViews içeren bir TabControl DoEvents kullanırken bazı çok garip sonuçlar yaşadı. Öte yandan, uğraştığınız tek şey ilerleme çubuğuna sahip küçük bir formsa, sorun olmayabilir.

Sonuç olarak: DoEvents kullanacaksanız, uygulamanızı dağıtmadan önce iyice test etmeniz gerekir.


Güzel bir cevap, ancak ilerleme çubukları için daha iyi bir çözüm önerebilirsem , diyorum ki, çalışmanızı ayrı bir iş parçacığında yapın, ilerleme göstergenizi değişken, kilitli bir değişkente kullanılabilir hale getirin ve ilerleme çubuğunuzu bir zamanlayıcıdan yenileyin . Bu şekilde, hiçbir bakım kodlayıcısı döngünüze ağır kod eklemek için cazip olmaz.
mg30rg

1
DoIvents veya eşdeğeri, büyük kullanıcı arayüzü işlemleriniz varsa ve kullanıcı arayüzünü kilitlemek istemiyorsanız kaçınılmazdır. İlk seçenek, büyük UI işleme parçaları yapmamaktır, ancak bu, kodu daha az sürdürülebilir hale getirebilir. Bununla birlikte, Dispatcher.Yield () yöntemi DoEvents ile çok benzer bir şey yapar ve esasen tüm kilitler ve amaçlar için ekranı kilitleyen bir UI işleminin zaman uyumsuz hale getirilmesine izin verebilir.
Melbourne Developer

11

Evet.

Ancak, kullanmanız gerekiyorsa Application.DoEvents, bu çoğunlukla kötü uygulama tasarımının bir göstergesidir. Belki de bunun yerine ayrı bir iş parçacığında bazı işler yapmak istersiniz?


3
başka bir iş parçacığında tamamlamak için işte dönebilir ve bekleyebilirsiniz böylece isterseniz?
jheriko

@jheriko O zaman gerçekten async-await denemelisin.
mg30rg

5

Yukarıda jheriko'nun yorumunu gördüm ve başlangıçta, başka bir iş parçacığında uzun süre çalışan bir eşzamansız kod parçasını bekleyen ana UI iş parçacığınızı döndürdüğünüzde DoEvents kullanmaktan kaçınmanın bir yolunu bulamadığımı kabul ediyordum. Ancak Matthias'ın cevabından, arayüzümdeki küçük bir panelin yenilenmesi DoEvents'ın yerini alabilir (ve kötü bir yan etkiyi önleyebilir).

Davam hakkında daha fazla detay ...

Bir ilerleme çubuğu türü açılış ekranı ( "yükleme" kaplaması nasıl görüntülenir ... ) uzun süre çalışan bir SQL komutu sırasında güncellendiğinden emin olmak için ( burada önerildiği gibi ) aşağıdaki yapıyordum :

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
}

Kötü: Benim için DoEvents'ı çağırmak, fare tıklamaları bazen açılış ekranımın arkasındaki formlara ateş ediyordu, hatta TopMost yaptım.

İyi / yanıt: DoEvents satırını açılış ekranımın ortasındaki küçük bir panele basit bir Yenile çağrısı ile değiştirin FormSplash.Panel1.Refresh(). Kullanıcı arayüzü güzel bir şekilde güncelleniyor ve başkalarının uyardığı DoEvents tuhaflığı ortadan kalktı.


3
Yenile, pencereyi güncellemez. Bir kullanıcı masaüstünde başka bir pencere seçerse, pencerenize geri tıklamanın bir etkisi olmaz ve işletim sistemi uygulamanızı yanıt vermiyor olarak listeler. DoEvents (), mesajlaşma sistemi üzerinden işletim sistemi ile etkileşime girdiğinden, yenilemeden çok daha fazlasını yapar.
ThunderGr

4

"DoEvents-Hack" kullanarak birçok ticari uygulama gördüm. Özellikle görüntü oluşturma söz konusu olduğunda bunu sıklıkla görüyorum:

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

Hepsi bu yöntemin kötülüğünü biliyor. Ancak, hack kullanıyorlar, çünkü başka bir çözüm bilmiyorlar. İşte alınan bazı yaklaşımlardır blog yayınında tarafından Tom Miller :

  • Formunuzu, tüm çizimin WmPaint'te gerçekleşmesini sağlayacak şekilde ayarlayın ve oluşturma işleminizi orada yapın. OnPaint yönteminin bitiminden önce bunu yaptığınızdan emin olun.Invalidate (); Bu, OnPaint yönteminin hemen yeniden başlatılmasına neden olur.
  • P / Win32 API içine çağırmak ve PeekMessage / TranslateMessage / DispatchMessage arayın. (Doevents aslında benzer bir şey yapar, ancak bunu ekstra tahsisler olmadan yapabilirsiniz).
  • CreateWindowEx çevresinde küçük bir paket olan kendi form sınıfınızı yazın ve ileti döngüsü üzerinde kendinize tam denetim sağlayın. - DoEvents yönteminin sizin için iyi çalıştığından emin olun ve buna uyun.


3

DoEvents, kullanıcının diğer olayları tıklatmasına veya yazmasına ve tetiklemesine izin verir ve arka plan iş parçacıkları daha iyi bir yaklaşımdır.

Ancak, olay iletilerini silmeyi gerektiren sorunlarla karşılaşabileceğiniz durumlar da vardır. Ne zaman kontrol işlemek için sıradaki iletileri vardı RichTextBox denetiminin ScrollToCaret () yöntemi yoksayıyordu bir sorunla karşılaştım.

Aşağıdaki kod, DoEvents çalıştırırken tüm kullanıcı girişlerini engeller:

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

1
Doevents'ı ararken olayları her zaman engellemelisiniz. Aksi takdirde, uygulamanızdaki diğer etkinlikler yanıt olarak tetiklenir ve uygulamanız aynı anda iki şey yapmaya başlayabilir.
HızlıAl

2

Application.DoEvents, mesaj kuyruğuna grafik işleme dışında bir şey konursa sorun oluşturabilir.

İlerleme çubuklarını güncellemek ve MainForm inşaat ve yükleme gibi bir şeydeki ilerlemeyi kullanıcıya bildirmek için yararlı olabilir, eğer bu biraz zaman alır.

Yaptığım son bir uygulamada, MainForm'umun yapıcısında bir kod bloğu her yürütüldüğünde bir Yükleme Ekranındaki bazı etiketleri güncellemek için DoEvents kullandım. UI iş parçacığı, bu durumda, SendAsync () çağrılarını desteklemeyen bir SMTP sunucusuna e-posta göndermekle meşgul oldu. Muhtemelen Begin () ve End () yöntemleri ile farklı bir iş parçacığı oluşturabilir ve onlardan bir Send () çağırdı olabilir, ancak bu yöntem hata eğilimli ve ben uygulama sırasında Ana Form inşaat sırasında istisna atma tercih etmem.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.