web-dev-qa-db-ja.com

Application.DoEvents()の使用

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

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

254
Craig Johnston

私の経験から、.NETでDoEventsを使用するときは十分に注意することをお勧めします。 DataGridViewを含むTabControlでDoEventsを使用すると、非常に奇妙な結果が生じました。一方、あなたが扱っているのがプログレスバー付きの小さなフォームだけであれば、それは問題ないでしょう。

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

13
Craig Johnston

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

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

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

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

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

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

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

C#およびVB.NETの次のバージョンは、新しいawaitおよびasyncキーワードを備えた別の銃を提供します。 DoEventsとスレッドに起因するトラブルに小さな部分からインスパイアされましたが、大部分は非同期操作の実行中にUIを最新の状態に保つためにrequiresというWinRTのAPIデザインに触発されました。ファイルから読み取るのと同じです。

454
Hans Passant

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

DoEventsは悪ですか?を参照してください。

直接 MSDNページ that thedev を参照:

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

だからマイクロソフトはその使用に対して警告します。

また、その振る舞いは予測不可能で副作用が起こりやすいので、ハックだと思います(これは、新しいスレッドをスピンアップしたりバックグラウンドワーカーを使用したりする代わりにDoEventsを使用しようとした経験によるものです)。

ここにマチスモはありません - それが強固な解決策として働いていたら私はそれをくまなく考えています。しかし、.NETでDoEventsを使用しようとすると、痛み以外何も起こらなくなりました。

27
RQDQ

はい、System.Windows.Forms名前空間のApplicationクラスには静的なDoEventsメソッドがあります。 System.Windows.Forms.Application.DoEvents()を使用すると、UIスレッドで長時間実行されるタスクを実行するときに、UIスレッドのキューで待機しているメッセージを処理できます。これには、長いタスクが実行されている間、UIの反応をよくし、「ロック」されていないように見せるという利点があります。しかし、これはほとんどの場合、物事を実行するための最良の方法ではありません。 Microsoftによると、DoEventsは「...待機中のウィンドウメッセージがすべて処理されている間、現在のスレッドを中断します。」イベントが発生した場合、追跡が困難な予期せぬ断続的なバグが発生する可能性があります。あなたが広範囲のタスクを持っているならば、それは別のスレッドでそれをすることがはるかに良いです。長いタスクを別のスレッドで実行すると、UIが円滑に実行され続けるのを妨げることなく、それらを処理できます。詳細は こちら をご覧ください。

ここ はDoEventsの使い方の例です。マイクロソフトはそれを使用しないように注意を払っていることに注意してください。

23
Bill W

はい。

ただし、Application.DoEventsを使用する必要がある場合、これは主にアプリケーション設計が正しくないことを示しています。多分あなたは代わりに別のスレッドでいくつかの仕事をしたいですか?

11

私は "DoEvents-Hack"を使って多くの商用アプリケーションを見ました。特にレンダリングがうまくいったとき、私はこれをよく見ます:

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

彼ら全員はその方法の悪について知っている。しかし、彼らは他の解決策を知らないので、彼らはハックを使います。これは ブログ投稿 by Tom Miller から取られたいくつかのアプローチです:

  • すべての描画がWmPaintで行われるようにフォームを設定し、そこでレンダリングを行います。 OnPaintメソッドが終了する前に、必ずthis.Invalidate()を実行してください。これにより、OnPaintメソッドがすぐに再び起動されます。
  • Win32 APIにP/Invokeし、PeekMessage/TranslateMessage/DispatchMessageを呼び出します。 (Doeventsは実際には似たようなことをしますが、余分な割り当てなしでこれを行うことができます)。
  • CreateWindowExを囲む小さなラッパーとなる独自のフォームクラスを作成し、メッセージループを完全に制御します。 - DoEventsメソッドはあなたのためにうまく機能し、それに固執することを決定します。
4
Matthias

私は上記のjherikoのコメントを見て、最初はメインのUIスレッドを回転させて別のスレッドで非同期のコードが長時間実行されるのを待てばDoEventsを使用しないようにする方法を見つけることができないことに同意しました。しかし、Matthiasの答えから私のUI上の小さなパネルの単純なRefreshが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と呼んでいたのは、TopMostにしても、スプラッシュスクリーンの背後にあるフォームでマウスクリックが発生することがあったことを意味します。

good/answer:DoEventsの行を、スプラッシュスクリーン中央の小さなパネルへの単純なRefresh呼び出しFormSplash.Panel1.Refresh()に置き換えてください。 UIはうまく更新され、他の人が警告していたDoEventsの奇妙さはなくなりました。

4
TamW

MSDNドキュメントで Application.DoEvents メソッドを調べてください。

3
thedev

グラフィック処理以外の何かがメッセージキューに入れられた場合、Application.DoEventsは問題を引き起こす可能性があります。

時間がかかる場合は、プログレスバーを更新したり、MainFormの構築や読み込みなどの進行状況をユーザーに通知するのに便利です。

私が最近作ったアプリケーションでは、私のMainFormのコンストラクターでコードのブロックが実行されるたびにDoEventsを使ってLoading Screenのラベルを更新しました。この場合、UIスレッドは、SendAsync()呼び出しをサポートしていないSMTPサーバーで電子メールを送信することに専念していました。 Begin()メソッドとEnd()メソッドを使って別のスレッドを作成し、それらからSend()を呼び出すこともできますが、このメソッドはエラーが発生しやすく、アプリケーションのメインフォームでは構築時に例外をスローしません。

2
Guest123

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

ただし、イベントメッセージをフラッシュする必要がある問題に遭遇する可能性がある場合がまだあります。私は、リッチテキストボックスコントロールがScrollToCaret()メソッドを無視していて、コントロールに処理するメッセージがキューに入っていたときに問題に遭遇しました。

次のコードは、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
    }
}
1