ButtonやTextBoxなどの基底クラスとなるControlクラスにも、BeginInvokeはある・・・。
Control.BeginInvoke メソッド (Delegate) (System.Windows.Forms)
これも非同期処理に関するものだが、C#コンパイラの用意するBeginInvokeとはちょっと違い、非同期処理を実行するワーカースレッドからコントロールの操作を行う時に使う・・・。
今回はWindowsアプリケーション・・・。
Buttonを、
大きく配置・・・。
フォントも24と大きく・・・。
テキストも、
"開始"に変更・・・。
button1のClickに対するハンドラは、
// button1がクリックされた時. private void button1_Click(object sender, EventArgs e) { // "開始"から"実行中..."へ. button1.Text = "実行中..."; // button1.Textに"実行中..."をセット. // 10秒待つ.(擬似的な重たい処理.) Thread.Sleep(10000); // Thread.Sleepで10秒休止. // "実行中..."から"開始"に. button1.Text = "開始"; // button1.Textに"開始"をセット. }
最初、こうしてみる・・・。
Thread.Sleepで10秒かかる疑似的に重たい処理、前後でテキストを変化させる・・・。
このように、重たい処理をUIスレッド上で行うと、テキストは変化しないし、固まってしまう・・・。
前回までにやったBeginInvokeやAsyncCallbackを使って、重たい処理を非同期化する・・・。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Runtime.Remoting.Messaging; // AsyncResultクラス(System.Runtime.Remoting.Messaging名前空間) using System.Text; using System.Threading; // マルチスレッド(System.Threading名前空間) using System.Windows.Forms; // デリゲートの定義 delegate void AsyncButtonDelegate(); // 引数も戻り地も無い関数を持つデリゲートAsyncButtonDelegate. namespace Control_ { public partial class Form1 : Form { public Form1() { InitializeComponent(); } // button1がクリックされた時. private void button1_Click(object sender, EventArgs e) { // 非同期処理で実行するデリゲートasyncButtonの生成. AsyncButtonDelegate asyncButton = new AsyncButtonDelegate(AsyncButton); // AsyncButtonを呼ぶAsyncButtonDelegateオブジェクトasyncButtonの生成. // 非同期処理の完了後に実行するAsyncCallbackデリゲートcallbackの生成. AsyncCallback callback = new AsyncCallback(AsyncCallbackFunc); // 非同期処理完了後にAsyncCallbackFuncを実行するAsyncCallbackオブジェクトcallbackを生成. // 非同期処理の開始. asyncButton.BeginInvoke(callback, null); // asyncButton.BeginInvokeで非同期処理開始(非同期処理完了後に実行するcallbackを渡している.) } // 非同期処理ボタンメソッドAsyncButton. public void AsyncButton() { // "開始"から"実行中..."へ. button1.Text = "実行中..."; // button1.Textに"実行中..."をセット. // 10秒待つ.(擬似的な重たい処理.) Thread.Sleep(10000); // Thread.Sleepで10秒休止. // "実行中..."から"終了"に. button1.Text = "終了"; // button1.Textに"終了"をセット. } // 非同期処理完了後のコールバックメソッドAsyncCallbackFunc. public void AsyncCallbackFunc(IAsyncResult ar) { // IAsyncResult型arをAsyncResultにキャスト. AsyncResult asyncRes = (AsyncResult)ar; // arをAsyncResult型asyncResにキャスト. // 非同期処理で使ったデリゲートを取得. AsyncButtonDelegate asyncButton = (AsyncButtonDelegate)asyncRes.AsyncDelegate; // asyncRes.AsyncDelegateをキャストしてasyncButtonを取得. // 非同期処理の完了. asyncButton.EndInvoke(ar); // asyncButton.EndInvokeで完了. // 3秒待つ.(元に戻す処理.) Thread.Sleep(3000); // Thread.Sleepで3秒休止. // "終了"から"開始"に. button1.Text = "開始"; // button1.Textに"開始"をセット. } } }
button1がクリックされたら、asyncButton.BeginInvokeでAsyncButtonが実行され、それが終ったら渡したcallbackが持つAsyncCallbackFuncが呼ばれる・・・。
しかし、これを実行すると、
このような例外が出てしまう・・・。
テキストを変更するなどのコントロールを操作する処理はUIスレッドで行わないといけないからである・・・。
(AsyncCallbackFuncはUIスレッドだと思っていたが、どうもこれも例外が出たのでワーカースレッドらしい・・・。)
そこでControl.BeginInvokeである・・・。
引数に渡したデリゲートが持つメソッドがそのControlのスレッド(UIスレッド)で実行されるのである・・・。
AsyncButtonの中で、button1.BeginInvokeにrunningを渡すと、Runningが実行されて"実行中..."になり、finishを渡すと、Finishが実行されて"終了"になり、AsyncCallbackFuncの中ではあるが、startを渡すとStartが実行されて"開始"になる・・・。
(今回は引数なしバージョンを使ったので、いっぱいデリゲート作ってしまったが、引数ありバージョンなら表示するテキストを渡すだけの1つのデリゲートで済む・・・。)
押すと、
"実行中..."になり、しばらく待つと、
"終了"となり、ちょっと待つと、
"開始"に戻る・・・。
Sample/dotnet/Control/BeginInvoke/src/Control_ at master · bg1bgst333/Sample · GitHub