スレッド処理について

解決


フォレスタ  2005-08-12 23:48:11  No: 58610

おせわになります。

ワーカスレッドを以下のように実行しています。
以下のコードでは、画面を表示すると同時に、
Main関数の長い処理を実行し、
処理が終了したら画面を閉じるということをしています。
(長い処理の間、画面中にプログレスバー表示)

以下のコードで、
Windows2000,VC++6.0,CPU(pentium3:750MHz程度)
の環境では正常に動作するのですが、
WindowsXP,VC++6.0,CPU(pentium4:3GHz程度)
の環境では確実に、ダイアログを閉じるタイミングで
プログラムが落ちてしまいます。

最初OSの問題かと思っていましたが、
WindowsXP,VC++6.0,CPU(pentium4:2GHz程度)
の環境では正常に動作するので、OSの問題ではないようです。

ということで、CPUがあやしいのではと思い調べたのですが、
エラーが発生する環境のCPUは、最新のCPUで、
「ハイパースレッディングテクノロジー」
が採用されたものですが、何かエラーと関係があるのでしょうか?

あと気になっているのは、スレッドを終了する前に、
OnCancel関数により画面を閉じる処理を入れているのですが
ここではまずいでしょうか?

どなたかご教授願います。

//ダイアログ表示時処理
BOOL CDlg::OnInitDialog() 
{
    CDialog::OnInitDialog();
    
    m_pThread_Main = AfxBeginThread(ThreadFunc_Main , this);
    return TRUE; 
}

//スレッドのスタティック関数
UINT CDlg::ThreadFunc_Main(LPVOID pParam)
{
      ((Cread_pv_result*)pParam)->Main();
      ((Cread_pv_result*)pParam)->OnCancel();    //ここではまずい?

      return 0;
}

void CDlg::Main()
{
      //スレッドのメイン処理
      ...

      m_pThread_Main = NULL;
}


てつや  2005-08-13 00:29:13  No: 58611

>((Cread_pv_result*)pParam)->OnCancel();
((CWnd*)pParam)->PostMessage(WM_COMMAND, IDCANCEL );
かな?(違っていたらごめんなさい)

サブスレッドよりも先にメインスレッドが終わってしまう可能性があります。
OnDestroy内にWaitForSingleObjectでサブスレッド終了を待った方が確実です。

サブスレッドからダイアログの関数呼び出すのもなんだかなあ。
排他処理が確実なら問題ないですが。


tetrapod  2005-08-13 00:46:24  No: 58612

自分のプログラムが間違っている可能性を棚に上げてCPUを疑うというのは
きわめて倣岸不遜な態度だと思います。で、実際バグってるし。

> ((Cread_pv_result*)pParam)->Main();
Cread_pv_result が何者かまったくわかりませんが CDlg の間違いとして。

OnCancel は「フレームワークによって呼び出される」関数なので、これを
自分で呼び出すのは大いに間違っています。こういう場合は EndDialog を使う。


エートリーぶ  2005-08-13 09:22:50  No: 58613

>フォレスタさんへ

>「ハイパースレッディングテクノロジー」
>が採用されたものですが、何かエラーと関係があるのでしょうか?
過去にCPUのバグが無かった訳ではありませんが、最新のCPUを
搭載しているマシンで、インターネットブラウザーのGUIが
通信している最中に動かなくなくなるというこはありますか?
万が一あれば、CPUのバグも考えられますが、メディアの報道でも
聞いたことがありません。(=CPUのバグはない)

ご存知とは思いますが「ハイパースレッディングテクノロジー」と
いうのは、CPUが本当に複数個のスレッドを同時に処理する技術です。
#シングルCPUでのマルチスレッドというのは、交互にスレッドを
#切り替えて、同時に処理していたように見せているだけです。

「ハイパースレッディングテクノロジー」の登場で
潜在的に不具合だったものが、顕在化したのでは?

てつやさんの↓
>サブスレッドからダイアログの関数呼び出すのもなんだかなあ。
同感です。

tetrapodさん・・・的を射てて・・・コメントが・・・
・・・倣岸不遜・・・読み?


てつや  2005-08-13 19:36:34  No: 58614

>OnCancel は「フレームワークによって呼び出される」関数なので、これを
>自分で呼び出すのは大いに間違っています。
これは確かにそうなのですが、
>こういう場合は EndDialog を使う。
これも危険。
スレッド毎にCHandleMapというクラスがHWNDとCWnd*の関係を管理しています。
サブスレッド内でEndDialogを使うとDestroyWindowが呼ばれますが、
サブスレッドのCHandleMapには該当するCWnd*がない。
メインスレッドのCHandleMapには既に破棄されたHWNDをもつCWnd*が存在する。
と言う理由で不都合が生じる可能性があります。
だからメインスレッドにメッセージをポストして、メインスレッド側で
DestroyWindowしてもらうのが確実です。


フォレスタ  2005-08-14 01:00:11  No: 58615

回答ありがとうございます。

いろいろご指摘いただいたのですが
知識不足のため理解ができていません。

整理させてください。

まず、メインスレッド、サブスレッドについてですが
サブスレッドは、メインスレッドの処理の中で
作られるものということでよろしかったでしょうか?
上記コードの「void CDlg::Main()」関数内で
AfxBeginThread()関数により作成されたものがサブスレッド?

初歩的な質問ですが、一歩一歩理解していきたいと思ってます。
よろしくお願いします。


てつや  2005-08-14 03:43:35  No: 58616

>上記コードの「void CDlg::Main()」関数内で
>AfxBeginThread()関数により作成されたものがサブスレッド?

ええ、そのつもりで書きましたが、サブスレッドという言い方は
一般的でないかもしれません。すみません。


フォレスタ  2005-08-14 06:47:02  No: 58617

もう一点確認させてください。

>だからメインスレッドにメッセージをポストして、メインスレッド側で
>DestroyWindowしてもらうのが確実です。

この説明の中で出てくる「メインスレッド側」は、
「サブスレッド側」ではないですか?

すみませんが、よろしくお願いします。


とおり  2005-08-14 14:53:58  No: 58618

>この説明の中で出てくる「メインスレッド側」は、
>「サブスレッド側」ではないですか?

なぜそう思ったのですか?
てつやさんの説明は、要するにWindowの生成/破棄は同じスレッドでやれということです。
理由は解説どおり、CWnd*は基本的にスレッド単位で管理されているから。
ダイアログを作ったのはサブスレッドではなく、メインスレッドですよね?


フォレスタ  2005-08-14 19:50:43  No: 58619

回答ありがとうございます。

>なぜそう思ったのですか?
まだ、正直全然理解できていないのですが、
今までの意見を見て判断しました。
「サブスレッドからダイアログの関数呼び出すのもなんだかなあ。」
という意見から単純にそう思ったのです。

>ダイアログを作ったのはサブスレッドではなく、メインスレッドですよね?
MDIなのですが、メニューボタンを押した時の処理を、
ビュークラスに記述していて、その中でCDlg(上記コードの)ダイアログ
モーダルで表示しています。
ダイアログ表示中は、長い処理が行われ、その間、プログレスバーを表示し、処理終了でダイアログを閉じています。

とううことで、ダイアログを作ったのは、スレッド処理の外となる
のですが、そもそも作りがおかしいということはないですか?

気づいた点があればご指摘ください。


てつや  2005-08-14 22:53:47  No: 58620

プログラムを作った時点で、まず1つのスレッドが生じます。これをメイン
スレッドといいます。( 多分この言い方は一般的 )
ですから、ダイアログはメインスレッドに存在しています。

AfxBeginThread()関数により作成された物をとりあえずサブスレッドという
事にしてください。

PostMessageはメッセージを送るだけで、そのメッセージハンドラの実行は
受け取った側(メインスレッド)で実行されます。
それに対し、
((Cread_pv_result*)pParam)->Main();
((Cread_pv_result*)pParam)->EndDialog();
のような関数の呼び出しは、呼び出した側(サブスレッド)で実行されます。

これを踏まえて、もう一度上の書き込みを参考にしてください。

>「サブスレッドからダイアログの関数呼び出すのもなんだかなあ。」
これは、
((Cread_pv_result*)pParam)->Main();
このやり方をさしています。

スレッドは、メモリ空間は同じですが、別のプログラムという風に考えてください。
"別プログラム(サブスレッド)の処理内容が、大元のプログラム(メインスレッド)に
あるダイアログのメンバ関数を実行するだけ。"というのがなんか違うような気が
して書きました。


エートリーぶ  2005-08-15 07:19:23  No: 58621

#前回の日本語の不味いところは、補って読んで頂くとしてm(_ _)m
#外していたら、さらにゴメンなさいだけれども・・・
#しかも、Xp  Proですね?  でないと以下は全部ハズレです。

シツコクなりますが、↓の理由だと思います。
「ハイパースレッディングテクノロジー」の登場で
潜在的に不具合だったものが、顕在化した。
以下、ここでは  ↓とします
「ハイパースレッディングテクノロジー」=「HTテクノロジー」

理由1.
「HTテクノロジー」を搭載しないCPUのときは、
(一見)正常に動いていた。

理由2.
「HTテクノロジー」を搭載するCPUのときは、ダイアログを
閉じるタイミングで、プログラムが落ちる。

不安視してたスレッド中での↓がまずいと思います。
((Cread_pv_result*)pParam)->OnCancel();
#私もワーカスレッドから、GUIのコントロール等は操作します。

理由
「サブスレッド(←私もこう表現するときもあります)」の中で、
その「スレッド」を生成したダイアログを消滅させようといる。

実際の動作としては「HTテクノロジー」を搭載しないCPUのときは
UINT CDlg::ThreadFunc_Main(LPVOID pParam)
↑の関数のコードが全て終了してから、ダイアログ消去の処理が
始まると思いますが、「HTテクノロジー」を搭載するCPUの
ときは、即時に始まる可能性があると思うからです。
つまり、スレッドの後処理が終了していない時点で、その後処理
ためのコードがなくなるのだから・・・まずいのでは?

それで、対策なのですが・・・

対策1
てつやさんの方法も一案と思います。
ダイアログ消去までの時間がかせげるので。

対策2
要は、「ワーカスレッド」の処理が終了したことが判明した時点で
それから、おもむろにダイアログ消去したいのだと思いますから
スレッド終了の判断の一方法を示します。

関数名等が違ったときは変えて、使って下さい。(大雑把ですから)
「CREATE_SUSPENDED」は必ず設定して下さい。
#これも、GUIの方は固まらないと思いましたが(記憶がややあいまい)。
#実はマルチポストですが、質問ではないので、ご了承ください。

static CWinThread *threadHndl = NULL;

中略

BOOL CDlg::OnInitDialog()  {

#if 0
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,
               int nPriority = THREAD_PRIORITY_NORMAL,
               UINT nStackSize = 0, DWORD dwCreateFlags = 0,
               LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

#endif
//  ↓は「dwCreateFlags」を設定してませんので、↑を参考に設定して下さい

  threadHndl = AfxBeginThread((AFX_THREADPROC)ThreadFunc_Main, (void *)this);
  if (NULL != threadHndl) {

    //  生成が成功したときのコードですが、今回は無いようです

  } else {

    MessageBox("スレッドの作成に失敗しました","報告", MB_OK);
          return  -1;

  }

  threadHndl->m_bAutoDelete = FALSE;  //  CREATE_SUSPENDED  を設定してないと
  threadHndl->ResumeThread();      //  意味がないかも・・・

  DWORD exitCode;
  do {
    AfxGetThread()->PumpMessage();
    ::GetExitCodeThread(threadHndl->m_hThread,&exitCode);
  
  } while (STILL_ACTIVE == exitCode);

  delete threadHndl;

  threadHndl = NULL;

  return  0;

}

参考資料:「MFCプログラミング」Alan R. Feuer  著  発行所(株)アスキー


とおり  2005-08-15 09:02:10  No: 58622

エートリーぶさんへのつっこみです。

>対策1
>てつやさんの方法も一案と思います。
>ダイアログ消去までの時間がかせげるので。

勘違いをしていませんか?
これはWindowの生成/破棄を同じスレッドで正しく行うための処置です。
時間稼ぎといったタイミングを変えるための処置ではありません。
そもそも、CWnd*は違うスレッドにそのまま渡して良いものではなく(HWNDはOK)
その点に関してのプログラミング規則に違反している点を対策するものです。

>#私もワーカスレッドから、GUIのコントロール等は操作します。

上記で書いたように、CWnd*はそのまま渡してはいけないはずです。
それこそ、そのバージョンのWindows上で"たまたま"動いているに過ぎず(=必ず大丈夫だと
保証されていない)、違うバージョンでも動く保証は全くありません。

MSDN上にも明記されていたはずですが見つからないので、とりあえずご参考。
http://forums.belution.com/ja/vc/000/104/41s.shtml
http://homepage2.nifty.com/DSS/VCPP/MFC/Thread/ThreadMFC.htm

そもそもHT固有の問題と判明したわけでもないのに、そこにフォーカスして
解説するのはおかしいと思います。(もちろんその可能性もあるが)
仮に、スレッド間の実行順序や実行タイミングに依存するバグが存在し、
これまでの通常の実行で問題が出ていないだけであれば、別にHTに限らず
普通のマルチプロセッサ環境でも問題が発現するかもしれません。
シングルプロセッサ、not HT環境でも、あるスレッドの実行が大きく沈み
込んでしまったような場合は、やはり発現する可能性があります。

単純に、スレッドの実行順序やタイミングに依存しているようなプログラム
は根本的にバグ持ちであり、環境によらず対策が必要です。


エートリーぶ  2005-08-15 19:43:35  No: 58623

>とおりさんへ
鋭い「つっこみ」ありがとうございますm(_ _)m
貴殿が指摘する内容は、その通りだと思います。
#普段は論理的な説明に定評がある常連の方と推察します。

>勘違いをしていませんか?
実は、ほとんど当たり。(小さな声にならないのは、残念)
この対策1については、深く考えていないことを見破られました。(汗
もっとも、てつやさんの趣旨が↓であることも理解しています。
これはWindowの生成/破棄を同じスレッドで正しく行うための処置です。

ご存知とは思いますが、モーダルダイアログの場合、消滅するまで
制御が他に移動できないし、この質問は、その点が不明なため、
深く考えても意味がないと、判断したしだいです。
#真相としては、私が大域的に考えるのが苦手なだけです、ゴメン。
#もちろん、不真面目に考えたという訳でもありません、念のため。

それで、「対策2」の方法に的を絞りますが、ワーカスレッドが
確実に終わった時点で、tetrapodさんが示された「EndDialog」等を
BOOL CDlg::OnInitDialog() の中で使えばいいのではないかと思います。
この方法でも、「Windowの生成/破棄を同じスレッドで正しく行う」と
いうプログラム構造の原則(?)は満たします。それに、何より
スレッドの中ではなくダイアログの中の使用なので、「CWnd」とか
「HWND」については、大域的に考えなくて良い利点があります。
つまり、プログラム構造として、局所的に収束している利点があると
いうことです。
#勘違いは、こちらの説明に比重をかけたかったからとも付け加えます。

>そもそもHT固有の問題と判明したわけでもないのに、そこにフォーカス
>して解説するのはおかしいと思います。(もちろんその可能性もあるが)
ですから、「潜在的に不具合だったものが、顕在化した」と申しています。
ロジックとして、危険であったコードが、「HTテクノロジー」の登場で
はっきりと「不具合」として表面化したとの認識です。

付け加えるなら、「遣ってダメなら、次の原因と方法を考えましょう!」
とも申しておきます。、前回の冒頭に↓のような文言も入れていますし。
>#外していたら、さらにゴメンなさいだけれども・・・
不具合の可能性のあるものを、一つ一つ潰していくことが、原因の特定と
対処につながると思います。

>単純に、スレッドの実行順序やタイミングに依存しているようなプログラム
>は根本的にバグ持ちであり、環境によらず対策が必要です。
まさしく、その通りだと思います。また、今後増えることが予想されます。
すでに、指摘されている「スレッド使用に起因する不具合」の典型です。

なお、示したコードは大雑把であることは、事前に、お断りしましたが
「return  数値」の部分は  ここでは  「EndDialog」ですね。

ところで、とおりさんは不具合の原因は何であると思いますか?
また、その対策はどのようにお考えでしょうか?

>フォレスタさんへの質問とお願い
質問は、このプログラムは、当初はシングルスレッドで作成されて
いたと推測しますが、GUIが固まるので、マルチスレッドに
したものではないでしょうか?
#作りからして、そう判断したくなります

お願いとしては、是非、確かめて結果を報告して下さい。m(_ _)m
#「ハイパースレッディングテクノロジー」機能搭載のCPUなど
#まだまだ高嶺(=高値)の花で、私には手が届きません。


isshi  2005-08-15 20:25:12  No: 58624

エートリーぶさん、そのソースは本当に動きましたか?

私の環境(Windows XP home sp1, VC++7.1)では動きませんでした。
(ダイアログが表示されない。)
OnInitDialog が完了していないのに、無理やり PumpMessage しても
まともに動くとは思えません。


エートリーぶ  2005-08-15 21:38:40  No: 58625

>isshiさんへ

>エートリーぶさん、そのソースは本当に動きましたか?
ご指摘有難うございます。指摘されて「あっ」と思いました。
実は、OnInitDialog() にダイレクトに入れて確かめた訳ではありません。
拙作ソフト↓で使っていますが、確かに初期化時ではありません。
http://www.vector.co.jp/soft/win95/edu/se362748.html
では、こちらも早とちりですね。うっかりしていました。m(_ _)m

ただし、スレッド終了の判断の方法としては使えると思いますので
なんらかのトリガーで、始動したいのですが・・・
トリガーのためにダイアログ内メソッドをを呼び、そこから
使う手段を考えたいですが、すぐには妙案が浮かばないです。
#遡って、画面とワーカスレッドの同時作成が可能かなとも考えます。
#であれば、念にため、GUIのマルチスレッドも紹介しておきます↓
http://cgi21.plala.or.jp/keny01/wforum/wforum.cgi?no=1879&reno=no&oya=1879&mode=msgview&page=0

もうし訳ないですが、スレ主のフォレスタさんには、すぐには妙案が
浮かばないので、役に立てばと思い  第三の対策
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200507/05070018.txt
を紹介します。

フォレスタさんへ別なお願いで、既に検証していると思いますが
最初に示されたコードの状況で
UINT CDlg::ThreadFunc_Main(LPVOID pParam)
      ((Cread_pv_result*)pParam)->OnCancel();    //ここではまずい?
      ↑のコードの削除をお願いします。ここで、これはまずいと思います。

これで落ちなければ、このコードが原因で落ちることが判明します。


フォレスタ  2005-08-15 22:19:25  No: 58626

回答ありがとうございます。

回答を参考に、以下のようにコードを修正したら、
プログラムが落ちなくなりました。

//キャンセルの実行をメインスレッド側で実行させるため変更。
((CDlg*)pParam)->OnCancel();
                ↓
((CDlg*)pParam)->PostMessage(WM_COMMAND, IDCANCEL );

//ダイアログを閉じる前にスレッドを確実に終了させるため追加
void Cread_pv_result::PostNcDestroy() 
{
  WaitForSingleObject( m_pThread_Main->m_hThread , INFINITE);
  
  m_pThread_Main = NULL;

  CDialog::PostNcDestroy();
}

上記コードで何か問題があればご指摘願いたいと思います。
なお、スレッドの変数(m_pThread_Main)はスレッド終了時、
deleteする必要はあるのでしょうか?


isshi  2005-08-15 22:42:19  No: 58627

>上記コードで何か問題があればご指摘願いたいと思います。
WM_COMMOND の IDCANCEL は以下の場合にも送られてきます。
  ・キャンセルボタン(IDCANCEL)が押されたとき
  ・ウィンドウ右上のクローズボタンが押されたとき
  ・ESC キーが押されたとき
  ・Alt + F4 が押されたとき
  (他にもあるかもしれない。)
これらをちゃんと考慮しているのであれば大丈夫です。
私はこれが面倒なので、独自のメッセージを定義して使うようにしています。

>なお、スレッドの変数(m_pThread_Main)はスレッド終了時、
>deleteする必要はあるのでしょうか?
AfxBeginThread の後、
  m_pThread_Main->m_bAutoDelete= FALSE;
としたなら必要です。


フォレスタ  2005-08-15 23:40:17  No: 58628

回答ありがとうございます。

>WM_COMMOND の IDCANCEL は以下の場合にも送られてきます。
>  ・キャンセルボタン(IDCANCEL)が押されたとき
>  ・ウィンドウ右上のクローズボタンが押されたとき
>  ・ESC キーが押されたとき
>  ・Alt + F4 が押されたとき

上2つに関しては、ボタンを配置していないので大丈夫です。
ESCキーにかんしては、下記コードで使用できないようにしています。

//####  ESCキーを無効  ####
BOOL CDlg::PreTranslateMessage(MSG* pMsg) 
{
  if(pMsg->message==WM_KEYDOWN)
    if(pMsg->wParam==VK_ESCAPE)  //エスケープキーを無効にする
    {
      return TRUE;
    }
  
    return CDlg::PreTranslateMessage(pMsg);
}

Alt + F4 キーに関しては、以下のコードをPreTranslateMessage関数
に追加して対処しました。

  if(pMsg->message==WM_SYSCOMMAND)
  {
    if(pMsg->wParam == SC_CLOSE)
    {
      return TRUE;
    }
  }

>AfxBeginThread の後、
>m_pThread_Main->m_bAutoDelete= FALSE;
>としたなら必要です。
m_pThread_Main->m_bAutoDeleteは変更していないので
TRUEのままですが、どのような場合にFALSEにする必要があるのでしょうか?
よろしくお願いします。


isshi  2005-08-16 00:42:30  No: 58629

>m_pThread_Main->m_bAutoDeleteは変更していないので
>TRUEのままですが、どのような場合にFALSEにする必要があるのでしょうか?
勝手に delete されては困るとき。

たとえば、AfxBeginThread の第1引数に CWinThread 派生のオブジェクトを
渡した場合、スレッドが終了した後に、そのオブジェクトが保持している
データを参照したいときなど。

フォレスタさんのソースで言えば、PostNcDestroy に来たときには
すでに delete されているかもしれず、オブジェクトを参照できない。

m_bAutoDelete = FALSE; にする場合は、エートリーぶさんが指摘しているように、
AfxBeginThread の dwCreateFlags に CREATE_SUSPENDED を指定する必要があります。
  m_pThread_Main = AfxBeginThread(..., CREATE_SUSPEND, ...);
  m_pThread_Main->m_bAutoDelete = FALSE;
  m_pThread_Main->ResumeThread();

そうしないと、
  m_pThread_Main->m_bAutoDelete = FALSE;
に来たときにはもうスレッドが終了しているかもしれないからです。


isshi  2005-08-16 01:01:17  No: 58630

よく考えたら、WaitForSingleObject でも m_pThread_Main を参照して
いるのですから、m_bAutoDelete = FALSE; は必須ですね。


フォレスタ  2005-08-16 03:29:24  No: 58631

回答ありがとうございます。

>よく考えたら、WaitForSingleObject でも m_pThread_Main を参照して
>いるのですから、m_bAutoDelete = FALSE; は必須ですね。
よくわかりました。
以下のように変更しました。

//ダイアログ表示時処理
BOOL CDlg::OnInitDialog() 
{
    CDialog::OnInitDialog();
    
    m_pThread_Main = AfxBeginThread(ThreadFunc_Main , this);

    m_pThread_Main->m_bAutoDelete = FALSE;    //追加
    m_pThread_Main->ResumeThread();            //追加

    return TRUE; 
}

//ダイアログを閉じる前にスレッドを確実に終了させるため追加
void Cread_pv_result::PostNcDestroy() 
{
  WaitForSingleObject( m_pThread_Main->m_hThread , INFINITE);
    
  delete m_pThread_Main;    //追加
  m_pThread_Main = NULL;

  CDialog::PostNcDestroy();
}


てつや  2005-08-16 03:57:10  No: 58632

>サブスレッドよりも先にメインスレッドが終わってしまう可能性があります。
>OnDestroy内にWaitForSingleObjectでサブスレッド終了を待った方が確実です。

こう書いたのはダイアログがメインウィンドウだと思ったからです。

メインウィンドウが別にあってダイアログをモーダルで出しているのなら
こんな事おこりません。(無理やりやればできるのかな。)

だからWaitForsingleObject自体いらないと思います。


フォレスタ  2005-08-16 04:17:03  No: 58633

回答ありがとうございます。

>こう書いたのはダイアログがメインウィンドウだと思ったからです。

>メインウィンドウが別にあってダイアログをモーダルで出しているのなら
>こんな事おこりません。(無理やりやればできるのかな。)

>だからWaitForsingleObject自体いらないと思います。

というのは、①の処理により発行した、ダイアログを閉じる処理は、
②のスレッド処理が終了しない限り、実行されないということでよかった
ですか?

//スレッドのスタティック関数
UINT CDlg::ThreadFunc_Main(LPVOID pParam)
{
    ((Cread_pv_result*)pParam)->Main();
    ((CDlg*)pParam)->PostMessage(WM_COMMAND, IDCANCEL );  ・・・①

    return 0;・・・②
}


isshi  2005-08-16 04:47:21  No: 58634

>    m_pThread_Main = AfxBeginThread(ThreadFunc_Main , this);
>
>    m_pThread_Main->m_bAutoDelete = FALSE;    //追加
>    m_pThread_Main->ResumeThread();            //追加
先ほども書いたように、AfxBeginThread で CREATE_SUSPENDED の指定が必要です。
  m_pThread_Main = AfxBeginThread(ThreadFunc_Main , this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);

>メインウィンドウが別にあってダイアログをモーダルで出しているのなら
>こんな事おこりません。(無理やりやればできるのかな。)
メインウィンドウからモーダルの CDlg を生成して、その CDlg のOnInitDialog で
サブスレッドを生成しているのだと思います。
そして、サブスレッド終了とともにそのモーダルダイアログを閉じたいのだと
思います。
その場合、モーダルダイアログが破棄される前に、WaitForSingleObject でサブスレッドの
終了待ちをするのは必要なことだと思います。

>というのは、①の処理により発行した、ダイアログを閉じる処理は、
>②のスレッド処理が終了しない限り、実行されないということでよかった
>ですか?
よくないです。


エートリーぶ  2005-08-16 05:09:01  No: 58635

>だからWaitForsingleObject自体いらないと思います。
念のため、あった方が確実ではないでしようか。
SendMessage( )  ではなくて、PostMessage(  )なので
大丈夫だとは思いますが・・・。

なくても、確実な保証があれば、それでも構いませんが、
理由を聞かせて欲しく思いますが、解決になったし・・・。
↓この辺りに記入して頂ければ
http://www.belution.com/lounge/ja/viewtopics.php?id=20050428152237

要は、フォレスタさんが差し支えなければ良いですが。

ただし、WaitForsingleObject  を使うのであれば、
isshiさん  ご指摘のように、CREATE_SUSPEND  を設定して下さい。
AfxBeginThread(..., CREATE_SUSPEND, ...);  ←  ここの話
こうしなければ↓のコードが意味をなさなくなります。
  m_pThread_Main->m_bAutoDelete = FALSE;
  m_pThread_Main->ResumeThread();
また、CREATE_SUSPEND(=  m_bAutoDelete = FALSE)を
設定する前提であれば、最後に  delete を必要とします。
逆の前提でなのに、delete  すると落ちます。

あと、ここの皆様に、お願いしたいことは
メインスレッド  =  プライマリスレッド(どちらでも可)
↑の表現を認めて頂きたいことです。
理由は  CreateProcess  での説明で  CREATE_SUSPENDED  を
設定したとき  The primary thread of the new process ・・・
という表現が出てきているからです。


てつや  2005-08-16 05:12:17  No: 58636

>その場合、モーダルダイアログが破棄される前に、WaitForSingleObject でサブスレッドの
>終了待ちをするのは必要なことだと思います。
ええ、厳密に言えばやったほうが良いと思いますが、現実的に①(まる1)から
②(まる2)までの間でメインウィンドウを閉じてメインスレッドを終了させる
事は至難の業かと。

>というのは、①の処理により発行した、ダイアログを閉じる処理は、
>②のスレッド処理が終了しない限り、実行されないということでよかった
>ですか?
これは違います。①(まる1)と②(まる2)の間にSleepをいれれば、その間だけ
スレッド終了は遅れます。
スレッド関数はstaticなはずなので、ダイアログがなくなっても大丈夫だと
思いますが、isshiさんは何か他の可能性を指摘しているのでしょうか?


isshi  2005-08-16 05:46:52  No: 58637

>スレッド関数はstaticなはずなので、ダイアログがなくなっても大丈夫だと
>思いますが、isshiさんは何か他の可能性を指摘しているのでしょうか?

まさに↓この可能性を考えての発言でした。
>現実的に①(まる1)から②(まる2)までの間でメインウィンドウを閉じて
>メインスレッドを終了させる事は至難の業かと。

手動でメインウィンドウを終了させる場合は、確かに至難の業ですが、
DoModal の戻り値によって即座にメインウィンドウを終了させることも
あるかもしれません。
もちろん、必要ないと思えばそこまでしなくても良いかもしれませんが。


フォレスタ  2005-08-16 18:53:06  No: 58638

回答ありがとうございます。

以下の点、変更点として漏れていました。
ご迷惑おかけしました。

m_pThread_Main = AfxBeginThread(ThreadFunc_Main , this);
              ↓
 m_pThread_Main = AfxBeginThread(ThreadFunc_Main , this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);


PATIO  2005-08-16 19:44:36  No: 58639

えー、既に終わっていますけれど、

最初の方でどなたかが書かれていたように
CWndクラスとそこから派生したクラスのポインタを別のスレッドに引き渡しては
だめです。
Microsoftがはっきりとそういう使い方をしてはいけないと書いているので
やめた方が良いです。既に書かれているますが、どうしても引き渡す必要が
あるのであれば、HWNDで引き渡すようにします。
今動いているのは実装上動くようになっているからですが、
Microsoftが行ってはいけないと書いている以上、動かなくなるようなシステム
改変を行ったとしても文句は言えません。
少なくとも保障されている方法で実現するべきです。


isshi  2005-08-16 20:02:47  No: 58640

>最初の方でどなたかが書かれていたように
>CWndクラスとそこから派生したクラスのポインタを別のスレッドに引き渡しては
>だめです。
↓ご参考
http://forums.belution.com/ja/vc/000/307/64s.shtml

個人的には、PATIOさんの仰ることに異論はありません。


エートリーぶ  2005-08-16 20:28:03  No: 58641

>CWndクラスとそこから派生したクラスのポインタを別のスレッドに
>引き渡してはだめです。
PATIOさんの趣旨は分かりました。
でも、そうしたらスレ主のような「画面中にプログレスバー表示」を
どう、実装しますか?    ↓この辺りで考えを聞きたく思います
http://www.belution.com/lounge/ja/viewtopics.php?id=20050428152237


isshi  2005-08-16 20:38:25  No: 58642

>でも、そうしたらスレ主のような「画面中にプログレスバー表示」を
>どう、実装しますか?
PostMessage すればよい。


てつや  2005-08-16 21:15:31  No: 58643

私が以前作ったのはこんな感じ。結構使いまわしてます。

#define WM_STOPTHREAD  WM_USER + 5050
#define WM_STEPTHREAD  WM_USER + 5051
/////////////////////////////////////////////////////////////////////////////
// CDoingDlg ダイアログ   ( だせえネーミング!! )

class CDoingDlg : public CDialog
{
// コンストラクション
protected :
  CDoingDlg(CWnd* pParent = NULL);   // 標準のコンストラクタ
public:
  CDoingDlg(CWnd* pParent ,int count , UINT(*thread)(LPVOID pParam) );
// ダイアログ データ
  //{{AFX_DATA(CDoingDlg)
  enum { IDD = IDD_DIALOG_DOING };
    // メモ: ClassWizard はこの位置にデータ メンバを追加します。
  //}}AFX_DATA
public :
  int GetError(){return m_iError ;};
  int GetEndCount(){return m_iEnd;};
protected:
  int m_iEnd ;
  int m_iError ;
  int m_iCount ;
  UINT (* m_thread )(LPVOID pPalam ) ;
  // 生成されたメッセージ マップ関数
  //{{AFX_MSG(CDoingDlg)
         //プログレスバーの初期化とスレッドスタート
  virtual BOOL OnInitDialog();
         //中止ボタン
  afx_msg void OnDoingStop();
         //矢印つき砂時計カーソル
  afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
  //}}AFX_MSG
         //プログレスバーのStepIt(WM_STEPTHREADのメッセージハンドラ)
  afx_msg LRESULT OnStep(WPARAM wParam , LPARAM lParam) ;
         //スレッド終了(WM_STOPTHREADのメッセージハンドラ)
   afx_msg LRESULT OnStop(WPARAM wParam , LPARAM lParam) ;
  DECLARE_MESSAGE_MAP()
};
extern volatile BOOL stop_thread ;

スレッドからはループ毎にWM_STEPTHREADを送ってもらう。
終了時にWM_STOPTHREADを送ってもらう。
中止ボタンを押したらグローバル変数のstop_threadをTRUEにする。
スレッドはstop_threadを確認したらWM_STOPTHREADを送って終了。
勿論スレッドに渡すのはHWND。スレッド内でCWnd::FromHandle使ってます。

isshiさんが言うように独自のメッセージを使うほうが便利です。
wParamやlParamを使って進捗と成功/失敗の情報も受け取れるから。

volatileははじめて知りました。


エートリーぶ  2005-08-16 21:18:58  No: 58644

>PostMessage すればよい。
なるほど。
有難うございます。
#一回覚えた知識・技術に固執しています。


tetuya  2005-08-16 21:31:44  No: 58645

ソースも短いので乗せてみました。

CDoingDlg::CDoingDlg(CWnd* pParent , int count , UINT(*thread)(LPVOID pParam) )
  : CDialog(CDoingDlg::IDD, pParent)
{
  m_thread = thread ;
  m_iCount = count ;
  m_iError = 0 ;
  m_iEnd   = 0 ;
  //{{AFX_DATA_INIT(CDoingDlg)
    // メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
  //}}AFX_DATA_INIT
}
BEGIN_MESSAGE_MAP(CDoingDlg, CDialog)
  //{{AFX_MSG_MAP(CDoingDlg)
  ON_BN_CLICKED(IDC_DOING_STOP, OnDoingStop)
  ON_WM_SETCURSOR()
  //}}AFX_MSG_MAP
  ON_MESSAGE(WM_STOPTHREAD , OnStop )
  ON_MESSAGE(WM_STEPTHREAD , OnStep )
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDoingDlg メッセージ ハンドラ

BOOL CDoingDlg::OnInitDialog() 
{
  CDialog::OnInitDialog();
  
  CProgressCtrl* p_pro = (CProgressCtrl*)GetDlgItem(IDC_DOING_PROGRESS) ;
  p_pro->SetRange(0,m_iCount) ;
  p_pro->SetStep(1) ;
  CString str ;
  str.Format(_T("%d/%d"),1,m_iCount) ;
  GetDlgItem(IDC_DOING_STATIC)->SetWindowText(str) ;
  ASSERT(m_thread) ;
  ::AfxBeginThread(m_thread,GetSafeHwnd()) ;
  return TRUE;
}
LRESULT CDoingDlg::OnStep(WPARAM wParam , LPARAM lParam)
{
  CString str ;
  str.Format(_T("%d/%d"),(int)wParam + 1 , m_iCount) ;
  GetDlgItem(IDC_DOING_STATIC)->SetWindowText(str) ;
  CProgressCtrl* p_pro = (CProgressCtrl*)GetDlgItem(IDC_DOING_PROGRESS) ;
  p_pro->StepIt() ;
  return 0 ;
}

LRESULT CDoingDlg::OnStop(WPARAM wParam , LPARAM lParam)
{
  m_iEnd = (int)wParam -(int)lParam; 
  m_iError = (int)lParam ;
  PostMessage(WM_CLOSE) ;
  return 0 ;
}

void CDoingDlg::OnDoingStop() 
{
  stop_thread = TRUE ;
}

BOOL CDoingDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
  SetCursor(LoadCursor(0,IDC_APPSTARTING)) ;
  return TRUE ;
}


てつや  2005-08-16 21:45:36  No: 58646

ちなみにスレッドで使う変数のたぐいは1つのクラスにまとめてグローバル
オブジェクトにします。(その方が排他制御が欠けやすいから。)


てつや  2005-08-16 21:47:02  No: 58647

>(その方が排他制御が欠けやすいから。)
その方が排他制御がかけやすい(簡単になる)から。


フォレスタ  2005-08-18 00:23:54  No: 58648

1点確認させてください。

>CWndクラスとそこから派生したクラスのポインタを別のスレッドに引き渡しては
>だめです。

というのは、
  AfxBeginThread(ThreadFunc_Main , this);
のthisを渡してはいけないということでしたか?


PATIO  2005-08-18 02:07:51  No: 58649

>AfxBeginThread(ThreadFunc_Main , this);
>のthisを渡してはいけないということでしたか?

そういうことになると思います。
細かい理由はMSDNに譲りますが、Microsoftは、CWndクラスのオブジェクトと
HWNDの対応を付けをスレッドローカルで行っており、この対応付けを
CWndの中のどの処理で使っているのかと言うところまで抑えていて
そこを避けるようにして使う分には現状は動作します。
但し、Microsoftはスレッド間でCWndクラスのオブジェクトをやり取りしては
いけませんと書いているので今後、動かないような方向に実装を変更したと
しても文句は言えないと思います。
そういう意味では、Microsoftの保障した方法で解決するように習慣付けた方が
安全でしょう。

PostMessageやSendMessageをしたいだけであれば、HWNDを渡せば十分に用は足りますから。

AfxBeginThread(ThreadFunc_Main, this->GetSafeHwnd());
とでもすればよろしいかと。
まあ、安全を考えるなら一度、HWNDの変数で取得しておいて
HWNDがNULLでないか確認した方が良いでしょうけれど。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加