このパートでは、スレッド間の同期メッセージを使用して、作業スレッドでエンジンの状態の管理と表示を整理する方法を例で示します。 また、アプリケーションを閉じるときにスレッドの相互ブロックの問題を回避する方法を示します。
前の記事の例に戻りましょう。 ワークフローでエンジンの状態を表示するグラフィカルインターフェイスがあります。 それに応じて、エンジンを開始、停止、一時停止、一時停止できるとします。 この動作を実装するには、有限状態マシンやオブザーバーなどをパターンに設計するのが最も簡単です。
最初に、エンジンの状態のセットを決定しましょう。 エンジンは非同期であるため、状態のセットには遷移状態もあります。 次の状態のセットを取得しました:NotStarted、StartPending、Started、PausePending、Paused、ResumePending、StopPending。
これらの状態は、主にGUIの状態を変更するために使用されます。 つまり、GUIはエンジンの状態の変化に関する通知を受け取り、それに応じてこの状態を表示します。 たとえば、NotStarted状態に切り替えると、GUIに「Start」ボタンが表示され、「Pause」、「Continue」、「Stop」ボタンがロックされます。 したがって、一時停止状態に切り替えると、開始ボタンと一時停止ボタンがロックされ、続行ボタンと停止ボタンのロックが解除されます。
エンジンの状態の変更に関する通知エンジンが、対応する制御ボタンを備えたWTLダイアログの例のようになる様子を見てみましょう。
void CMainDlg::OnStateChanged(EngineState::State state) { // , GUI CSyncChannel, // // Pending , , // GetDlgItem(IDC_BUTTON_START).EnableWindow(FALSE); GetDlgItem(IDC_BUTTON_STOP).EnableWindow(FALSE); GetDlgItem(IDC_BUTTON_PAUSE).EnableWindow(FALSE); GetDlgItem(IDC_BUTTON_CONTINUE).EnableWindow(FALSE); switch (state) { case EngineState::NotStarted: GetDlgItem(IDC_BUTTON_START).EnableWindow(TRUE); break; case EngineState::Started: GetDlgItem(IDC_BUTTON_STOP).EnableWindow(TRUE); GetDlgItem(IDC_BUTTON_PAUSE).EnableWindow(TRUE); break; case EngineState::Paused: GetDlgItem(IDC_BUTTON_STOP).EnableWindow(TRUE); GetDlgItem(IDC_BUTTON_CONTINUE).EnableWindow(TRUE); break; } }
その状態を管理するために、エンジンには適切な一連の関数があります:Start、Pause、Resume、Stop。ダイアログクラスでは、対応するボタンクリックハンドラーから呼び出されます。
同期メッセージを使用することにより、ある状態から別の状態への遷移は、GUIスレッドに関して同期的に実行されます。 つまり、GUIスレッドが「開始」ボタンをクリックするためのハンドラーにある間、エンジンは非同期で開始または一時停止状態に切り替えることができず、ハンドラーが「開始」ボタンをクリックして完了するのを待ちます。 これにより、エンジンの状態管理が大幅に簡素化されます。
StartPendingなどの保留状態への移行は、Startなどの制御関数の呼び出し内で実行されるため、Start関数を終了すると、エンジンはStartPending状態になります。 つまり、Start関数の呼び出しが完了する前であっても、StartPending状態への遷移の通知は同期的に呼び出されます。
エンジンの実装を見てみましょう。
エンジンクラスを完全に持ち込みます マルチスレッドで作業する場合、不足している部分が大きな役割を果たす可能性があります。
//Engine.h namespace EngineState { enum State { NotStarted, StartPending, Started, PausePending, Paused, ResumePending, StopPending }; }; class IEngineEvents { public: virtual void OnStateChanged(EngineState::State state) = 0; }; class CEngine { public: CEngine(IEngineEvents* listener); ~CEngine(void); public: // void Start(); void Stop(); void Pause(); void Resume(); public: // GUI bool ProcessMessage(MSG& msg); private: void WaitForThread(); static DWORD WINAPI ThreadProc(LPVOID param); void Run(); bool CheckStopAndPause(); void ChangeState(EngineState::State state); void OnStateChanged(EngineState::State state); private: CSyncChannel m_syncChannel; // IEngineEvents* m_listener; HANDLE m_hThread; volatile EngineState::State m_state; };
// Engine.cpp CEngine::CEngine(IEngineEvents* listener) : m_listener(listener), m_hThread(NULL), m_state(EngineState::NotStarted) { m_syncChannel.Create(GetCurrentThreadId()); } CEngine::~CEngine(void) { // , m_state = EngineState::StopPending; // , . // GUI, . // , , // m_syncChannel, . m_syncChannel.Close(); // . WaitForThread(); } void CEngine::WaitForThread() { if (m_hThread) { // . // : // StopPending NotStarted _ASSERT(m_state == EngineState::StopPending || m_state == EngineState::NotStarted); // DWORD waitResult = WaitForSingleObject(m_hThread, INFINITE); _ASSERT(waitResult == WAIT_OBJECT_0); // , HANDLE CloseHandle(m_hThread); m_hThread = NULL; } } void CEngine::Start() { // , GUI // , // if (m_state == EngineState::NotStarted) { // Start , , // // , WaitForThread(); // , // this, Run m_hThread = CreateThread(NULL, 0, CEngine::ThreadProc, this, 0, NULL); if (m_hThread) { // StartPending // ChangeState(EngineState::Started) // , SyncChannel, // , // , "Start" // StartPending , // Started ChangeState(EngineState::StartPending); } } } void CEngine::Stop() { // , GUI if (m_state != EngineState::NotStarted && m_state != EngineState::StopPending) { // , ChangeState(EngineState::StopPending); } // , // // ChangeState(EngineState::Stopped) // WaitForThread } void CEngine::Pause() { // , GUI if (m_state == EngineState::Started) { // PausePending Started ChangeState(EngineState::PausePending); } } void CEngine::Resume() { // , GUI if (m_state == EngineState::Paused) { // ResumePending Paused ChangeState(EngineState::ResumePending); } } bool CEngine::ProcessMessage(MSG& msg) { // GUI return m_syncChannel.ProcessMessage(msg); } DWORD WINAPI CEngine::ThreadProc(LPVOID param) { // , // Run reinterpret_cast<CEngine*>(param)->Run(); return 0; } void CEngine::Run() { // GUI , ChangeState(EngineState::Started); for (;;) { // , - // if (!CheckStopAndPause()) { break; } // - , :) Sleep(1000); } // , ChangeState(EngineState::NotStarted); // NotStarted // - // WaitForSingleObject } bool CEngine::CheckStopAndPause() { // . // GUI. if (m_state == EngineState::StopPending) { // Stop, return false; } if (m_state == EngineState::PausePending) { // , // GUI // ChangeState(EngineState::Paused); while (m_state == EngineState::Paused) { Sleep(100); } if (m_state == EngineState::StopPending) { // Stop, return false; } // // GUI ChangeState(EngineState::Started); } // return true; } void CEngine::ChangeState(EngineState::State state) { // , // GUI , m_syncChannel m_syncChannel.Execute(boost::bind(&CEngine::OnStateChanged, this, state)); } void CEngine::OnStateChanged(EngineState::State state) { // GUI , m_state // m_state // , // GUI m_state = state; m_listener->OnStateChanged(m_state); }
CEngineクラスを使用します。
私の例では、CEngineクラスのオブジェクトは、ダイアログのWTLクラスのメンバーとして宣言されています。
ダイアログ機能のセットは、CEngineオブジェクトの対応する機能を呼び出すStart、Stop、Continue、Pauseキーを押すためのハンドラーに縮小されます。
また、ダイアログクラスは、IEngineEventsインターフェイスを使用してエンジンの状態の変化の通知をサブスクライブします;このインターフェイスのOnStateChanged関数の実装は、記事の冒頭で説明しました。
ストリームメッセージがCEngineクラスに確実に変換されるように、ダイアログクラスは標準のWTL :: CMessageLoop :: AddMessageFilterメソッドを使用してウィンドウメッセージフィルターとして自身を設定し、WTL :: CMessageFilterインターフェイスを実装します。
class CMessageFilter { public: virtual BOOL PreTranslateMessage(MSG* pMsg) = 0; };
PreTranslateMessage関数の実装は、CEngine :: ProcessMessage関数の再呼び出しになります
アプリケーションを閉じるときにフローが相互にブロックされる問題を解決するために、ダイアログクラスで追加のアクションは必要ありません。 この問題は、CEngineクラスデストラクタでCSyncChannel :: Closeを使用することで完全に解決されました。 したがって、ワークフローはCEngineクラス内に完全にカプセル化されており、そのようなクラスで作業するときに具体的なメリットが得られます。