Application.DoEvents()の使用


272

Application.DoEvents()C#で使用できますか?

この関数は、VB6が行うのとほとんど同じ方法で、GUIがアプリの残りの部分に追いつくことを可能にする方法DoEventsですか?


35
DoEventsWindowsフォームの一部であり、C#言語ではありません。このように、それができる任意の.NET言語から使用します。ただし、.NET言語からは使用しないください
Stephen Cleary

3
2017年です。非同期/待機を使用してください。詳細については、@ hansPassantの回答の最後を参照してください。
FastAl

回答:


468

Hmya、DoEvents()の永続的な神秘性。それに対しては大きな反発がありましたが、なぜそれが「悪い」のかを説明する人はいません。「構造体を変異させない」と同じ種類の知恵。えーっと、ランタイムと言語で構造体の変更がサポートされていないのはなぜですか?同じ理由:あなたが正しくしないと、あなたは自分の足を撃ちます。簡単に。そして、それを正しく行うには、それが何をするかを正確に知る必要があります。DoEvents()の場合、これは間違いなく簡単には理解できません。

すぐに使える:ほとんどすべてのWindowsフォームプログラムには、実際にはDoEvents()への呼び出しが含まれています。これは巧妙に偽装されていますが、ShowDialog()という名前が異なります。アプリケーションの残りのウィンドウをフリーズせずにダイアログをモーダルにすることができるのはDoEvents()です。

ほとんどのプログラマーは、DoEventsを使用して、独自のモーダルループを作成するときにユーザーインターフェイスがフリーズするのを防ぎます。それは確かにそうです。Windowsメッセージをディスパッチし、ペイント要求を配信します。しかし問題はそれが選択的でないことです。ペイントメッセージをディスパッチするだけでなく、他のすべても配信します。

そして、問題を引き起こす一連の通知があります。モニターの前約3フィートから来ます。たとえば、ユーザーはDoEvents()を呼び出すループの実行中にメインウィンドウを閉じることができます。それは機能し、ユーザーインターフェイスはなくなっています。しかし、コードは停止せず、ループを実行しています。それは良くないね。ものすごく悪い。

さらにあります。ユーザーは、同じメニュー項目またはボタンをクリックして、同じループを開始させることができます。これで、DoEvents()を実行する2つのネストされたループがあり、前のループは中断され、新しいループは最初から始まります。それはうまくいくかもしれませんが、男の子の確率はわずかです。特に、ネストされたループが終了して中断されたループが再開すると、すでに完了しているジョブを終了しようとします。それが例外で爆破しない場合、データは確実にすべてスクランブルされて地獄に行きます。

ShowDialog()に戻ります。DoEvents()を実行しますが、他のことを行うことに注意してください。これは、アプリケーション内のすべてのウィンドウを無効にし、ダイアログ以外の、。3フィートの問題が解決されたので、ユーザーはロジックを混乱させるために何もすることができません。ウィンドウを閉じるとジョブを再開するという失敗モードの両方が解決されます。別の言い方をすれば、ユーザーがプログラムに別の順序でコードを実行させる方法はありません。コードをテストしたときと同じように、予測どおりに実行されます。ダイアログは非常に煩わしくなります。ダイアログをアクティブにして、別のウィンドウから何かをコピーして貼り付けることができないのは誰が嫌いですか?しかし、それは価格です。

これは、コードでDoEventsを安全に使用するために必要なことです。すべてのフォームのEnabledプロパティをfalseに設定することは、問題を回避するための迅速かつ効率的な方法です。もちろん、実際にこれを好むプログラマはいない。そして、そうではありません。これが、DoEvents()を使用してはならない理由です。スレッドを使用する必要があります。彼らはあなたにカラフルで不可解な方法であなたの足を撃つための方法の完全な武器をあなたに渡しますが。しかし、自分の足だけを撃つという利点があります。(通常)ユーザーが彼女を撃つことはできません。

C#とVB.NETの次のバージョンでは、新しいawaitキーワードとasyncキーワードを備えた別の銃を提供します。DoEventsとスレッドによって引き起こされたトラブルに一部影響を受けていますが、大部分は非同期操作が行われている間UIを更新し続ける必要があるWinRTのAPI設計に影響を受けています。ファイルからの読み取りのように。


1
ただし、これは氷山の一角にすぎません。Application.DoEventsコードにいくつかのUDP機能を追加するまで問題なく使用してきました。その結果、ここで説明する問題が発生しました。私はそれを回避する方法があるかどうかを知りたいですDoEvents
darda 2013年

これは良い答えです。私が見逃していると感じているのは、実行を譲る、スレッドセーフなデザイン、またはタイムスライシング全般についての説明/ディスカッションだけです。:)
jheriko

5
一般的に「スレッドを使用する」よりも実用的な解決策の恩恵を受けるでしょう。たとえば、BackgroundWorkerコンポーネントはスレッドを管理し、フットシューティングのカラフルな結果のほとんどを回避します。また、最先端のC#言語バージョンを必要としません。
Ben Voigt 2013年

29

それは可能ですが、ハックです。

参照されDoEvents関数悪か?

以下からの直接MSDNのページであることthedev参照:

このメソッドを呼び出すと、待機中のすべてのウィンドウメッセージが処理されている間、現在のスレッドが中断されます。メッセージによってイベントがトリガーされると、アプリケーションコードの他の領域が実行される可能性があります。これにより、アプリケーションのデバッグが困難な予期しない動作が発生する可能性があります。時間がかかる操作や計算を実行する場合は、新しいスレッドでそれらの操作を実行することが望ましい場合がよくあります。非同期プログラミングの詳細については、「非同期プログラミングの概要」を参照してください。

そのため、Microsoftはその使用について警告しています。

また、その動作は予測不可能で副作用が発生しやすいため、ハッキングと見なします(これは、新しいスレッドを起動したり、バックグラウンドワーカーを使用したりする代わりにDoEventsを使用した経験から生じます)。

ここにはmachismoはありません。それが堅牢なソリューションとして機能した場合、私はそれをすべて解決します。しかし、.NETでDoEventsを使おうとすると、痛みだけが生じます。


1
その投稿は.NET 2.0より前の2004年のものでありBackgroundWorker、「正しい」方法を簡略化するのに役立ちました。
ジャスティン

同意した。また、.NET 4.0のタスクライブラリもかなり優れています。
RQDQ 2011

1
マイクロソフトがしばしば必要とするまさにその目的のための機能を提供しているのなら、なぜそれはハックなのでしょうか?
クレイグジョンストン

1
@Craig Johnston-DoEventsがハッカーの範疇に入ると私が信じる理由をより詳しく説明するために私の回答を更新しました。
RQDQ 2011

そのコーディングホラー記事では、「DoEvents spackle」と呼ばれていました。鮮やかさ!
ゴンゾブレイン2013

24

はい、System.Windows.Forms名前空間のApplicationクラスに静的DoEventsメソッドがあります。System.Windows.Forms.Application.DoEvents()を使用すると、UIスレッドで長時間実行されるタスクを実行するときに、UIスレッドのキューで待機しているメッセージを処理できます。これには、長いタスクの実行中にUIの応答性が向上し、「ロック」されないように見えるという利点があります。ただし、これはほとんどの場合、最善の方法ではありません。MicrosoftがDoEventsを呼び出すと、「...待機中のすべてのウィンドウメッセージが処理されている間、現在のスレッドが中断される」としています。イベントがトリガーされると、追跡が困難な予期しない断続的なバグが発生する可能性があります。大規模なタスクがある場合は、別のスレッドで実行することをお勧めします。長いタスクを別のスレッドで実行すると、UIのスムーズな実行を妨げることなく、タスクを処理できます。見て詳細はこちら

以下はDoEventsの使用例です。Microsoftはそれを使用しないように注意も提供していることに注意してください。


13

私の経験から、.NETでDoEventsを使用する場合は十分に注意することをお勧めします。DataGridViewsを含むTabControlでDoEventsを使用すると、非常に奇妙な結果が発生しました。一方、処理するのがプログレスバー付きの小さなフォームの場合は、問題ないかもしれません。

つまり、DoEventsを使用する場合は、アプリケーションをデプロイする前に徹底的にテストする必要があります。


良い答えですが、プログレスバーの問題の解決策を提案する可能性がある場合は、別のスレッドで作業を行い、揮発性のインターロックされた変数で進行状況インジケーターを使用できるようにし、タイマーからプログレスバーを更新します。このようにして、メンテナンスコーダーが重いコードをループに追加しようとすることはありません。
mg30rg 2018年

1
大量のUIプロセスがあり、UIをロックしたくない場合は、DoEventsまたは同等のものは避けられません。最初のオプションは、UI処理の大きなチャンクを行わないことですが、これによりコードの保守性が低下する可能性があります。ただし、await Dispatcher.Yield()はDoEventsと非常によく似ており、基本的に、画面をロックするUIプロセスを、すべてのインテントと目的を非同期にできるようにします。
メルボルンデベロッパー、

11

はい。

ただし、を使用する必要がある場合Application.DoEvents、これは主にアプリケーション設計が不適切であることを示しています。おそらく、代わりに別のスレッドでいくつかの作業を行いたいですか?


3
スピンして作業を別のスレッドで完了するまで待機できるようにしたい場合はどうすればよいですか?
jheriko

@jherikoそれなら、あなたは本当にasync-awaitを試すべきです。
mg30rg 2018年

5

上記のjherikoのコメントを見て、最初にメインUIスレッドをスピンさせて、別のスレッドで長時間実行される非同期のコード部分が完了するのを待っている場合、DoEventsの使用を回避する方法を見つけることができないことに同意しました。しかし、Matthiasの回答から、UIの小さなパネルを単純に更新すると、DoEventsを置き換えることができます(厄介な副作用を回避できます)。

私のケースの詳細...

私は次のようにして(ここで提案されているように)、進行中のSQLコマンドの実行中にプログレスバータイプのスプラッシュスクリーン(「読み込み中」のオーバーレイを表示する方法...)が確実に更新されるようにしました。

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
}

悪い点: DoEventsを呼び出すと、スプラッシュスクリーンの背後にあるフォームでマウスクリックが発生することがありました。

良い答え: DoEvents行を、スプラッシュスクリーンの中央にある小さなパネルへの単純なRefresh呼び出しに置き換えますFormSplash.Panel1.Refresh()。UIが適切に更新され、他の人が警告していたDoEventsの奇妙さがなくなった。


3
ただし、更新してもウィンドウは更新されません。ユーザーがデスクトップ上の別のウィンドウを選択した場合、ウィンドウに戻ってクリックしても効果はなく、OSはアプリケーションを無応答としてリストします。DoEvents()は、メッセージングシステムを介してOSと対話するため、更新だけではありません。
ThunderGr 2013

4

「DoEvents-Hack」を使用して、多くの商用アプリケーションを見てきました。特にレンダリングが関係する場合、私はこれをよく目にします

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

彼らは皆、その方法の悪さを知っています。ただし、他の解決策を知らないため、ハックを使用します。ここから取られたいくつかのアプローチですブログの記事によるトム・ミラーは

  • すべての描画がWmPaintで発生するようにフォームを設定し、そこでレンダリングを実行します。OnPaintメソッドが終了する前に、必ずthis.Invalidate();を実行してください。これにより、OnPaintメソッドがすぐに再び発生します。
  • Win32 APIにP / Invokeし、PeekMessage / TranslateMessage / DispatchMessageを呼び出します。(Doeventsは実際に同様のことを行いますが、追加の割り当てなしでこれを行うことができます)。
  • CreateWindowExの小さなラッパーである独自のフォームクラスを記述し、メッセージループを完全に制御できるようにします。-DoEventsメソッドが適切に機能することを確認し、それを使い続ける


3

DoEventsを使用すると、ユーザーは他のイベントをクリックまたは入力してトリガーすることができます。バックグラウンドスレッドの方が適しています。

ただし、イベントメッセージのフラッシュが必要な問題が発生する場合があります。RichTextBoxコントロールが処理するメッセージをキューに持っていると、ScrollToCaret()メソッドを無視してしまうという問題に遭遇しました。

次のコードは、DoEventの実行中のすべてのユーザー入力をブロックします。

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を呼び出すときは、常にイベントをブロックする必要があります。そうしないと、アプリの他のイベントが応答して発生し、アプリが2つの処理を同時に開始する可能性があります。
FastAl

2

Application.DoEventsは、グラフィックス処理以外のものがメッセージキューに置かれると、問題を引き起こす可能性があります。

時間がかかる場合は、プログレスバーを更新し、MainFormの構築や読み込みなどの進捗状況をユーザーに通知するのに役立ちます。

私が作成した最近のアプリケーションでは、コードのブロックがMainFormのコンストラクターで実行されるたびに、ロード画面のいくつかのラベルを更新するためにDoEventsを使用しました。この場合、UIスレッドは、SendAsync()呼び出しをサポートしていないSMTPサーバーでの電子メールの送信に占有されていました。おそらくBegin()メソッドとEnd()メソッドで別のスレッドを作成し、それらからSend()を呼び出した可能性がありますが、そのメソッドはエラーが発生しやすく、アプリケーションのメインフォームが構築中に例外をスローしないことを望みます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.