ボタンを押すと、フォームが固まった#
昨日、ボタンに連続作業をさせるコードを入れて、ボタンを押すとその作業が終わるまでフォーム内の他の操作ができなくなってしまった。
ボタンにはwhile(true){}ループが入れてある。
これが原因であることは直感でわかったが、どうすればいいのかわからなかった。いろいろネットを調べたらサブスレッドからやればいいと書いてあった。それしか書いてないので、あとは自分でやり方を見つけるしかない。
サブスレッド、BeginInvoke、デリゲートで#
まずフォームが固まるのは理由として、
UI フリーズが発生する原因は、メッセージループ上(UI スレッド上)で長時間処理を行ってしまうことである。イベントハンドラなどに長時間処理を記述すると、OS からの再描画要求が実行されなくなり、UI がフリーズする。
とマイクロソフトのページに書いてある。さらに、
UI がフリーズしないアプリを作るためには、時間のかかる処理(具体的には 0.1sec 以上かかる処理)を別スレッドに切り離して実行しなければならない。
同じスレッド内ではなく、別のスレッドを作成してそこから指令を出すとフォームが更新され、操作が可能になるという。これをフォームのマルチスレッド処理というらしい。Windowsアプリ開発では当たり前の作業らしいのだが、他のサイトではあまり詳しくは掲載されていなかった。
しかし、
別スレッドから当該スレッドを操作してはいけないと書いてある。どうすればいいのか?
メイン以外のスレッドから UI コントロールを操作してはならない。このような場合には、BeginInvoke() 命令を使って、UI スレッド上で画面描画更新処理を動作させる。
とある。
BeginInvokeを使えばサブで作ったスレッドから指示を出せることになる。以下にまとめる。
(コード内にあるデリゲートとは使節団、使節といった意味になる。使節(delegate)がBeginInvokeに下のメッセージを運ぶといった意味かなと思う)
//MyFormというクラスを使う前提で記述してあります。
using namespace Threading;//名前空間を追加する(サブスレッド作成用)
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {//ボタンクリック
buttun1->Enable = false;//ボタン1を無効化する。
Thread ^subThread = gcnew Thread(gcnew ThreadStart(this,&MyForm::subthread)) ;//サブスレッド作成
subThread->IsBackground = true;//サブスレッドをバックグラウンド化する
subThread->Start();//サブスレッドを立ち上げる。
}
private: System::Void subThread() {//上記で作成したサブスレッドを記述する。
while(true) {//処理の長い作業やループを記述する。
BeginIvoke(gcnew delegate_of_shori_1(this,&MyForm::Shori_1));//ループ内でインボークを入れる。
}
BeginInvoke(gcnew delegate_of_shori_2(this,&MyForm::shori_2)); //ボタンを有効にするデリゲート。
}
private: delegate System::Void delegate_of_shori_1();//ループ内処理のデリゲート。shori_1
private: System::Void Shori_1() {//引数がある場合、()に記述する。上記デリゲートと引数をそろえる。
//ループ内で処理する内容。高速でループしているので重い処理は厳禁。軽い作業のみ記述する。
}
private: delegate System::Void delegate_of_shori_2();//ループ終了後のデリゲート。shori_2
private: System::Void shori_2{
//ループ後の処理を記述。
button1-> Enable = true;//ボタン1を有効化する。
}