Application.DoEvents()
C#で使える?
この機能は、VB6 とほぼ同じように、GUI がアプリの残りの部分に追いつくことを可能にする方法DoEvents
ですか?
うーん、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 設計に触発されました。ファイルからの読み取りのように。
それは可能ですが、それはハックです。
DoEvents は悪ですか?を参照してください。.
thedevが参照した MSDN ページから直接:
このメソッドを呼び出すと、待機中のすべてのウィンドウ メッセージが処理される間、現在のスレッドが中断されます。メッセージによってイベントがトリガーされる場合、アプリケーション コードの他の領域が実行される可能性があります。これにより、デバッグが困難な予期しない動作がアプリケーションで発生する可能性があります。時間がかかる操作や計算を実行する場合は、多くの場合、それらの操作を新しいスレッドで実行することをお勧めします。非同期プログラミングの詳細については、「非同期プログラミングの概要」を参照してください。
そのため、Microsoft はその使用に対して警告しています。
また、その動作は予測不可能であり、副作用が発生しやすいため、ハックと見なします (これは、新しいスレッドをスピンアップしたり、バックグラウンド ワーカーを使用したりする代わりに、DoEvents を使用しようとした経験から来ています)。
ここにはマチスモはありません - それが堅牢なソリューションとして機能するなら、私はそれで十分です. しかし、.NET で DoEvents を使おうとしても、苦痛しかありませんでした。
はい、System.Windows.Forms 名前空間の Application クラスに静的な DoEvents メソッドがあります。System.Windows.Forms.Application.DoEvents() を使用して、UI スレッドで実行時間の長いタスクを実行するときに、UI スレッドのキューで待機しているメッセージを処理できます。これには、UI の応答性が向上し、長いタスクの実行中に「ロックアップ」されないように見えるという利点があります。ただし、これはほとんどの場合、最善の方法ではありません。Microsoftによると、DoEventsを呼び出すと、「...待機中のすべてのウィンドウメッセージが処理されている間、現在のスレッドが中断されます。」イベントがトリガーされると、追跡が困難な予期しない断続的なバグが発生する可能性があります。大規模なタスクがある場合は、別のスレッドで実行する方がはるかに優れています。別のスレッドで長いタスクを実行すると、スムーズに実行し続ける UI に干渉することなく、タスクを処理できます。見て詳しくはこちら。
DoEvents の使用方法の例を次に示します。Microsoft は、使用に対する警告も提供していることに注意してください。
私の経験から、.NET で DoEvents を使用する場合は十分に注意することをお勧めします。DataGridView を含む TabControl で DoEvents を使用すると、非常に奇妙な結果がいくつか発生しました。一方、プログレス バーのある小さなフォームだけを扱っている場合は、問題ない可能性があります。
つまり、DoEvents を使用する場合は、アプリケーションをデプロイする前に徹底的にテストする必要があります。
はい。
ただし、 を使用する必要がある場合Application.DoEvents
、これはほとんどの場合、アプリケーションの設計が不適切であることを示しています。おそらく、代わりに別のスレッドでいくつかの作業をしたいですか?
上記の 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 の奇妙さは解消されました。
「DoEvents-Hack」を使用した多くの商用アプリケーションを見てきました。特にレンダリングが始まるとき、私はこれをよく見ます:
while(running)
{
Render();
Application.DoEvents();
}
彼らは皆、その方法の悪さを知っています。ただし、他の解決策を知らないため、ハックを使用します。以下は、 Tom Millerによるブログ投稿から取られたいくつかのアプローチです。
- すべての描画が WmPaint で行われるようにフォームを設定し、そこでレンダリングを行います。OnPaint メソッドが終了する前に、必ず this.Invalidate(); を実行してください。これにより、OnPaint メソッドがすぐに再起動されます。
- P/Win32 API を呼び出し、PeekMessage/TranslateMessage/DispatchMessage を呼び出します。(実際には Doevents も同様のことを行いますが、追加の割り当てなしでこれを行うことができます)。
- CreateWindowEx の小さなラッパーである独自のフォーム クラスを作成し、メッセージ ループを完全に制御できるようにします。-DoEvents メソッドが適切に機能すると判断し、それを使い続ける。
メソッドについては、MSDN ドキュメントを参照してくださいApplication.DoEvents
。