[VC2005]処理中のボタン押し受付

[戻る]
修行中 2010/10/23(土) 00:02:49
VisualStudio2005(C++)を使用しています。

Form画面に2つボタンを作り、1つのボタンが押され、ある長い処理を実行中に
もう1つのボタンを押すことで「強制中断」を行えるようにしたいのですが、
実際に試してみると、1つのボタンを押した処理中は、もう片方のボタン押し処理を
受け付けず、キューに入ってしまいます。

これを、1つ目のボタンの処理に割り込む形で、ボタン押し処理を
受け付けるようにできないでしょうか?


ちなみに、1つ目のボタンを押したときの処理は
AfxBeginThread()でスレッド処理にし、スレッドの要所要所で
CEventクラスで作成した変数(グローバル領域に作成)の監視を行うようにしています。

強制中断用のボタンを押したときにSetEvent()でCEventクラスの変数に
イベントをセットし、イベントがセットされたことをスレッドで確認し次第
スレッドでの処理を終了し、強制終了へ持って行くよう考えています。

ご教授やアドバイス等、よろしくお願いします。

PATIO 2010/10/25(月) 10:46:26
動かしている環境はXPですか?
それともVista? Windows7?
スレッドの挙動に関してはVista以降とVistaより前で少し変わっています。
例えば、各スレッドの動く順番に関して公式には順番に動く事を保証して
いませんが、XP以前では単純にセマフォ等を使う事でうまくスイッチできる
ケースが結構有りました。それでも100%ではなかったはずですけれど。
Vista以降は本当に仕様通りになっていてほっとくと特定のスレッドのみが
連続で動いてしまうケースもあるみたいです。
マルチスレッドと言っても完全に同時に動いているわけでは無いので
特定のスレッドが動作中は他のスレッドは止まっています。
動作しているスレッドの切替をこまめにする事で同時に動いているように
見せているわけです。

とはいえ、完全にワーカースレッドに持っていかれるという状況になるとも
思えないので確認ですが、ワーカースレッドが動いている時にウインドウを
移動したり、他のウインドウを重ねてからどかしてみたりした時に
ウインドウの再描画等は行われていますか?
もしそれすら行なわれていないとしたらマルチスレッド化の手法その物に
問題があるかもしれません。

一番気になるのは、実行ボタンを押した時の処理の中に全ての処理が終わるまで
終わらないようなループがないかと言う事です。
ループ内の処理だけをスレッド化しても効果は有りません。
ループごとワーカースレッド側に出してしまい、
メインスレッド側はワーカースレッドを起動したら
さっさと関数を抜けてしまう必要があります。

あと、ワーカースレッドのプライオリティを弄っていないかですね。
ワーカースレッド側のプライオリティを上げすぎるとメインスレッドが
滞ってしまう可能性はあると思いますよ。

PATIO 2010/10/25(月) 11:08:20
From画面と書かれていますが、もしかしてC++/CLIですか?
C++/CLIでやっているなら.NET Frameworkの方のマルチスレッド
プログラミングを調べてみるのも手かと思いますけれど。

C++/CLIの場合、.NET Frameworkとネイティブの混合が可能では
有りますけれど、混ぜないで済むなら.NET Frameworkの方で
やった方が良いのではと言う気もしてます。

修行中 2010/10/26(火) 20:33:15
PATIOさん、回答ありがとうございます。

動かしている環境はXPです。
From画面はC++/CLIではなく、MFCで作成しています。

>ワーカースレッドが動いている時にウインドウを移動したり、
>他のウインドウを重ねてからどかしてみたりした時に
>ウインドウの再描画等は行われていますか?

行われていませんでした。
スレッドを立ち上げたあと、スレッドを立ち上げたのと同じ
関数内で、スレッドの監視を1秒ごとに行い、スレッドが正常に
終了するかどうかをチェックしています。

ですのでPATIOさんが危惧しておられる通り、
メインスレッド側で「ワーカースレッドを起動したら
さっさと関数を抜けてしまう」ことができていませんでした。

これが原因でForm画面に処理が戻らず、ボタンのクリックを
検知できていないようです。

(1)ワーカースレッドを動かしつつ
(2)メインスレッドでワーカースレッドの監視を行いつつ
(3)画面のボタン押しを受け付けたい

…のですが、(2)と(3)とは、相容れないものなのでしょうか。

(2)と(3)を共存させる方法を調べてみたところ、
メインスレッド側に、VBの「DoEvents」に相当する処理を
入れてやり、一時的にForm画面に処理を戻してやる、というのが
あったのですが、(2)と(3)の処理を共存させるような場合、
このような方法が一般的なのでしょうか。

他に良い方法があれば、ご教授お願いします。

ryo 2010/10/26(火) 21:07:32
「メインが、サブの終了をずっと監視し続ける」
のではなく、
「サブが終了時に、メインに知らせる(メッセージを送る)」
ようにすればいい

ワーカースレッドから、さらにサブのワーカーをスレッド複数・・・みたいな場合は
そのまま待ってもいいけど、
メイン、というかユーザーインターフェースを担当してるスレッドが
サブスレッドの終了を待つのは避けるべき

#そいや、CFromViewとかありましたな

PATIO 2010/10/27(水) 09:59:23
ごめん、FromじゃなくてFormだった。

綴り間違いに関してはさておき、既にryoさんが書かれている通りで
メインスレッドは他の処理も受け付けたい訳ですから
ワーカースレッドの監視に張り付いているわけにはいきません。
ですからメインスレッドはアイドル状態にして置いて
ワーカースレッドの方から状況の変化を通知するようにするのが
一般的な構成になります。
終了が知りたいのであれば、ワーカースレッド側から通知するようにするわけです。

一番簡単なのは、メインスレッドで表示しているウインドウのハンドルを
ワーカースレッドに渡してワーカースレッドからウインドウメッセージで
通知するパターンですね。ユーザー定義のウインドウメッセージを使う事に
なります。ポイントはウインドウハンドルを渡すこと。
MFCのウインドウのインスタンスはスレッド間でやり取りしてはいけないと
Microsoftが公式に言っていますのでインスタンスやそのポインタを渡しては
いけません。ウインドウハンドルを渡してそのハンドルを使って
PostMessageします。
終了ステータスくらいならLPARAMとWPARAMがあるので渡せるはずです。
メインスレッド側では引き渡したウインドウにメッセージが飛んでくるので
メッセージを受けた時点で処理をするようにします。

PATIO 2010/10/27(水) 10:58:07
ちなみにですが、VBのDoEventに相当するような処理を入れるのであれば、
マルチスレッドにする意味がなくなるので止めた方がいいです。
マルチスレッドにしないでオペレーションを受け付けたり
画面の更新をしたい場合なんかにそれ相当の処理を入れる事が
ありますが、私個人としてはそのやり方は好きでは無いのでお勧めしません。
これはメインのメッセージループに処理を返しているのではなくて
別にメッセージループを作成してメッセージ処理を強行してしまう方法です。

メッセージループをメイン以外の箇所にも入れる事になるので
一つのアプリの中にメッセージループが複数個所に入る事になります。
本来なら一箇所でメッセージの取り回しをするのが基本だと思っているので
正直、このやり方は好きになれません。

まあ、私個人の考えなので無理強いは出来ないと思っていますが。

修行中 2010/10/30(土) 22:16:43
[[解決]]
ryoさん、PATIOさん、回答ありがとうございます。

確かにPATIOさんの仰る通り、DoEventに相当する処理を入れるのであれば
マルチスレッドを作成する意義が失われてしまいますね。

ryoさん、PATIOさんのご指摘通り、ワーカスレッドが
終了通知をメインスレッドに投げる方法が最も腑に落ちました。

(1)メインスレッドでは処理開始のボタン押しを受け付けた後
  ワーカスレッドを立ち上げ、その後すぐに関数を抜ける。
(2)ワーカスレッド動作中にメインスレッドではボタン押し処理を受け付ける。
(3)ワーカスレッドは終了したらLPARAMに終了ステータスを設定して
  メインスレッドに終了通知を行う。

…という流れで機能を実現させたいと思います。
ryoさん、PATIOさん、貴重なご意見、ありがとうございました。
また機会があれば、宜しくお願いします。

古いスレッドには追加発言できません。