処理が終わった後にフラグをおろしてダイアログを閉じる処理を作りたい。そこで以下のようなコーディング
を行ったのですが、Debugモードでは期待通りの動作をしてくれたものの、
Releaseモードでは、操作禁止ダイアログが表示されたままになりました。
この問題についての指摘とアドバイスを下さい。宜しくお願いします。
public Boolean bThreadFlag = false;
private Thread mtestThread;
private void startThread(){
bThreadFlag = true;
mtestThread = new Thread(new ThreadStart(testRun));
mtestThread.SetApartmentState(ApartmentState.STA);
mTestThread.Start();
}
private void testRun{
//ダイアログ表示
while(bThreadFlag){
//処理中
}
//ダイアログ終了
}
private void プログラム計算処理(){
//外部とのやり取りをする処理
//フラグを落とす
bThreadFlag = false;
}
http://msdn.microsoft.com/ja-jp/library/aa289523(v=vs.71).aspx#vbtchusingthreadsanchor7
このあたりを参考に
ループの場所で
System.Windows.Forms.Application.DoEvents();
とかで、明示的に他のイベントを発生可能なようにしてやらないと
駄目だったはずです。
デバッグモードのときは、デバッグする必要性があるので
そういうのを入れなくてもイベントが発生するようになってるので
動作してるんだと思います。
処理を別スレッドで行うとき、
ユーザインターフェースの操作を
そのスレッドで行うべきではありません。
この場合であれば Testrun は別スレッドとなるので、
testrun 内でユーザインターフェースに関する操作を行うべきではありません。
これは基本事項です。
別スレッドで処理中に何も操作をできないようにするのなら
別スレッドで処理を開始する前、
この例で言うなら startThread メソッドの
mTestThread.Start();
の手前で別に作成した閉じることのできないフォームを表示し、
別スレッドでの処理が終わるまで待機します。
別スレッドでの処理が完了したら表示したダイアログをとじます。
別スレッドでの処理と終了までの大気については
http://msdn.microsoft.com/ja-jp/library/ms228969(v=VS.90).aspx
この項目を読めばよいと思います。
おおざっぱにぱっと考えたことを書いたので
きちんと動く物を作るにはもうすこし苦しんでもらうことになるかもしれません。
ただ、処理中に何もできないようにするのなら、
あえて別スレッドにする必要はないかと思います。
// ユーザが閉じることのできないダイアログを表示
// なにか処理
// 表示したダイアログを表示
だけでいけるのではないでしょうか。
回答有難う御座います。
補足いたします。
以下二つのプロセスがあり、次に示す流れで動作します。
・サブGUIスレッド(操作禁止ダイアログ)
・メインスレッド(メイン処理)
「処理の流れ」
1:メインスレッドがユーザの操作を受け付ける
2:サブGUIスレッドが進捗表示する
3:メインスレッドが処理を実行する
4:メインスレッドが処理を完了した段階で、サブGUIスレッドの中止フラグをたてる。
曖昧な説明で申し訳ありません。
以下の処理は、メインスレッドから行う処理になります。
private void プログラム計算処理(){
//外部とのやり取りをする処理
//フラグを落とす
bThreadFlag = false;
}
ウィンドウ制御は基本的にイベントドリブン。
フラグに頼る限り問題は起きると思います(ここを乗り切ったとしても、ちょっと処理を入れたりバグ迂回したりしたらまた問題噴出すると思います)。
操作禁止ダイアログを作成した時にウィンドウハンドルを保持しておいて、計算が終わったらPostMessage()でメッセージを飛ばし、操作禁止ダイアログがそのメッセージを受け取ったら終了する。
という処理にしない限り、この場を乗り切ったとしてもバージョンアップ時などで問題が起き、そのたびに悩むだろうと思われます。
---
「2:サブGUIスレッドが進捗表示する」のやり方も気になりますね。
あんまり細かいタイミングで表示をしてると、その表示だけでCPUを使い過ぎて重くなる事があります。
・メインスレッドで5~10%単位でサブGUIに計算結果を送る
・サブGUIスレッドでタイマーを実装し、ある程度の単位の時間で進捗具合を取得して表示する
のどちらかでないと、重くなりそうな気がします。
http://yokohama.cool.ne.jp/chokuto/urawaza/api/PostMessage.html
回答有難う御座います。
質問当初、サブGUIスレッドに対してデータ更新通知などを出してあげれば、サブGUIスレッドから操作禁止ダイアログを終了できると考えていたのですが、、サブGUIスレッドに対してデータ更新を伝える方法はないでしょうか?
(操作禁止ダイアログのウインドハンドルは編集なしに取得できない状態です。)
コメントとしてかけないのでこっちに。
はっきりとした根拠を持って言うわけではないのですが、
独立したダイアログと言っても、
GUI関連は全部一つのスレッドで処理した方が良いかなって気はします。
操作禁止ダイアログはメインスレッド(UIスレッド)で表示し、
メインスレッドで行っている処理を別スレッドで行い、
必要に応じて、操作禁止ダイアログ.begininvoke で
進捗状況更新するのではだめなんでしょうか。
http://blogs.msdn.com/b/nakama/archive/2009/04/07/part-3-ui.aspx
回答有難う御座います。
あとで試してみます。
(回答回数増やしました)
http://msdn.microsoft.com/ja-jp/library/aa289523(v=vs.71).aspx#vbtchusingthreadsanchor7
このあたりを参考に
ループの場所で
System.Windows.Forms.Application.DoEvents();
とかで、明示的に他のイベントを発生可能なようにしてやらないと
駄目だったはずです。
デバッグモードのときは、デバッグする必要性があるので
そういうのを入れなくてもイベントが発生するようになってるので
動作してるんだと思います。
ずばり回答でした。ありがとうございます。
>サブGUIスレッドに対してデータ更新を伝える方法はないでしょうか?
>(操作禁止ダイアログのウインドハンドルは編集なしに取得できない状態です。)
ウィンドウハンドルは取得しておきましょう。
今回は「計算結果が取得できるまでウィンドウを出しておく」という処理ですが、この先ダイアログを閉じる契機なんていくらでも増えると思いますよ。
計算が何の計算なのか判らないので憶測で書きますが、計算ミス、データ取得先が壊れた、メモリ取得エラー、計算元データが入ってるファイル読み取り不可、データ矛盾、などなど、エラールートを考えたら「計算が終わったらダイアログを閉じる」という単純な作りには出来ないと思います。
ウィンドウハンドルを取る・取らないに関わらず、編集は絶対にするのだから、取得しておかないと、今回を乗り切ったとしても乗りきれるだけで今後が苦しくなる一方だと思いますよ。
あと、編集内容はたった2ラインですよね?現在ローカルで取得してるウィンドウハンドルをカットして、クラス定義の場所にペーストする、たったこれだけのエディットを何故「編集が必要」と抵抗してしまうのでしょうか。
ウィンドウハンドルをとったら、前回書いたPostMessage()関連でイベントを渡せばほぼ解決です。
yossiy7さんの回答についてお返事します。
1:
操作禁止ダイアログの編集は共通部品なので編集しずらい都合があります。
そのため検討の優先順位は
ややさげています。
ですが、「ウインドウズハンドルを受け取る方法を提供してもらう
ことができれば」「Postの受け口を作ってもらえれば」
間違いのない方法なのでこちらのアプローチも心見てみます。
2:
細かい進捗の更新をしたことがありますが、描画が追いつかないということがありました。
ご助言どおり、感覚を適当にあけるように注意します。
>操作禁止ダイアログの編集は共通部品なので編集しずらい都合があります。
編集しにくいのは「操作禁止ダイアログ」だったんですか?
例えば操作禁止ダイアログが出ている時に[esc]キーを押したり[Enter]キーを押すとどうなりますか。消えるようなら、そのダイアログはOnClose()関数を継承したままなので、windowcloseイベントを投げればいいだけのような気がします。
>「ウインドウズハンドルを受け取る方法を提供してもらうことができれば」
>「Postの受け口を作ってもらえれば」
ここですが、流石に操作禁止ダイアログのクラスは自分で持っていますよね?クラスの関数のPostMessage()使えばいいんじゃないですか?CDialogクラスを継承せずに操作禁止ダイアログが作られているとは思えないです。
---
分かりやすく書くと
操作禁止ダイアログがCSousaKinshiDialogクラスだったとしたら
CSousaKinshiDialog cDialog;
cDialog.PostMessage( WM_CLOSE );
という感じでいけると思います(引数の数が間違っているかも知れませんが、残りはNULLで大丈夫だと思います)。
---
上記を書く際に発見したすごい事。
親ダイアログが消える時、子ダイアログも消えます。
それを利用して、不可視ダイアログを作成してそのダイアログの子ダイアログとして編集禁止ダイアログを作成し、編集禁止ダイアログを消したい時に不可視ダイアログを消す、という荒業でも出来ない事は無いなと気が付きました。
イレギュラーなやり方なので、PostMessage()を使うやり方でやるべきだとは思いますが。
操作禁止ダイアログはFormを継承しています。たしかに継承元のCloseメソッドを呼び出せば消すことができるかもしれませんね。
親ダイアログを消して子ダイアログを消去する荒業もあるんですか、、、参考になります。
ずばり回答でした。ありがとうございます。