MFCの基礎 - マルチスレッド

提供:MochiuWiki : SUSE, EC, PCB
2021年11月18日 (木) 09:18時点におけるWiki (トーク | 投稿記録)による版 (文字列「<source lang」を「<syntaxhighlight lang」に置換)
ナビゲーションに移動 検索に移動

概要

MFCアプリケーションにおいて、マルチスレッドを使用する方法について記述する。

作成手順

まず、クラスにワーカースレッド関数(静的)とワーカースレッド本体の関数を追加する。
ワーカースレッド本体の関数では、静的でないメンバ変数およびメンバ関数が使用できる。
ワーカースレッド関数では、静的でないメンバ変数およびメンバ関数が使用できないと考えている人がいるが、
下記のサンプルコードのようにすればクラスの関数はワーカースレッド関数にて行うことができる。

サンプルコード

<syntaxhighlight lang="cpp">
// CFileView.h
class CFileView
{
   private:
      CWinThread *m_pLoadXMLThread

   public:
      static UINT LoadXMLThreadFunc(void* pParam);	// XMLファイル読み込み時のスレッド制御関数
      void        LoadXMLThreadFunc();			// XMLファイル読み込みスレッド処理関数
};
</source>


<syntaxhighlight lang="c++">
// CFileView.cpp
void CFileView::OnButtonClick()
{
   // XML読み込み処理スレッド関数を実行
   m_pLoadXMLThread = ::AfxBeginThread(LoadXMLThreadFunc, this, THREAD_PRIORITY_NORMAL,
                                       0, CREATE_SUSPENDED, NULL);
   ASSERT(m_pLoadXMLThread);
   if(m_pLoadXMLThread)
   {
      m_pLoadXMLThread->m_pMainWnd	= this;
      m_pLoadXMLThread->m_bAutoDelete	= FALSE;

      // スレッド処理の開始
      m_pLoadXMLThread->ResumeThread();
      while(WaitForSingleObject(m_pLoadXMLThread->m_hThread, 100) == WAIT_TIMEOUT)
      {
         // フリーズしないようにする
         MSG msg;
         while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
         {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
         }
      }
      delete m_pLoadXMLThread;
   }
   // マルチスレッドの応用(ウェイトウィンドウを表示する)
   //m_pLoadXMLThread = ::AfxBeginThread(LoadXMLThreadFunc, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL);

   //if(m_pLoadXMLThread)
   //{
   //   CRect rc;
   //   HWND hNotifyWnd = CreateDialog(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDD_PROCESSING), this->m_hWnd, (DLGPROC)YCommonProc);
   //   ::ShowWindow(hNotifyWnd, SW_SHOW);
   //   ::GetClientRect(&rc);
   //   ::MoveWindow(hNotifyWnd, (rc.right - rc.left) / 2 - 100, (rc.bottom - rc.top) / 2 - 25, 200, 50, TRUE);

   //   m_pLoadXMLThread->m_pMainWnd	= this;
   //   m_pLoadXMLThread->m_bAutoDelete = FALSE;

   //   m_pLoadXMLThread->ResumeThread();
   //   while(::WaitForSingleObject(m_pLoadXMLThread->m_hThread, 100) == WAIT_TIMEOUT)
   //   {
   //      // フリーズしないようにする
   //      MSG msg;
   //      while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
   //      {
   //         ::TranslateMessage(&msg);
   //         ::DispatchMessage(&msg);
   //      }
   //   }
   //   delete m_pLoadXMLThread;

   //   ::DestroyWindow(hNotifyWnd);
   //}
}

//----------------------------------------------------------------------------------
// ワーカースレッド関数(静的)
// @brief	  XMLファイル読み込み時のスレッド関数
// @param[in]	  pParam : 通常CFileViewクラスへのポインタが渡される
// @description  [説明]
//               XMLファイル読み込み処理を別スレッドで行う。
//               ここでは処理本体を呼ぶだけである。
// @return	  成功時 0 を返します。
//----------------------------------------------------------------------------------
UINT CFileView::LoadXMLThreadFunc(void *pParam)
{
   CFileView *pFileView = dynamic_cast<CFileView*>(reinterpret_cast<CWnd*>(pParam));
   if(pFileView)
   {
      pFileView->LoadXMLThreadFunc();
   }
   return THREAD_EXIT_CODE;
}

// ワーカースレッド本体の関数
// スレッドの開始
void CFileView::LoadXMLThreadFunc()
{
   // スレッド本体の処理
}
</source>



注意事項

UpdateData(TRUE)およびUpdateData(FALSE)等はマルチスレッド内で呼ぶことができない。

AfxGetMainWnd()の戻り値は、m_pLoadXMLThread->m_pMainWndに渡したウィンドウである。
設定しないとNULLが返り、また、ダイアログのCreate関数も呼ぶことができない。
そこで、スレッドの処理と同期してUIを更新する場合(UpdateData(TRUE)をスレッドから同期して呼ぶ場合)は、
下記のようにスレッド内からメインウィンドウにメッセージを送る。

<syntaxhighlight lang="cpp">
// stdafx.h

//------------------------------------------------------------------------
// USER_MESSAGE
//------------------------------------------------------------------------
#define WM_USER_COMPLETE_LOAD_XML   (WM_USER + 1)   // XMLデータの読み込みの完了メッセージ
</source>


<syntaxhighlight lang="c++">
// CFileView.cpp

// スレッドからメイン処理にメッセージを送る & 受け取って処理
BEGIN_MESSAGE_MAP(CFileView, CDockablePane)
   // UserMessage
   ON_MESSAGE(WM_USER_COMPLETE_LOAD_XML, &CFileView::OnCompleteLoadXML)
END_MESSAGE_MAP()

// ワーカースレッド関数の本体にて
// XMLファイルの読み込みが完了したメッセージを送信
AfxGetMainWnd()->SendMessage(WM_USER_COMPLETE_LOAD_XML, (WPARAM)&firstIndex, (LPARAM)firstFilePath.GetString());
</source>


<syntaxhighlight lang="c++">
// CFileView.h

class CFileView
{
   // 追加
   afx_msg LRESULT OnCompleteLoadXML(WPARAM wParam, LPARAM lParam );
}
</source>


後は、SendMessage関数を受け取った側で処理を行えばよい。
受け取り側では、UpdateData(TRUE)およびUpdateData(FALSE)等を呼ぶことができる。

ここでスレッドを待つWaitForSingleObject関数を使用した場合、デッドロックになるので気をつけること。
スレッドの終了時に何らかの処理を行うのであれば、今回のようにスレッド終了時に"メッセージをメインウィンドウへ送る"ことで解決できる。