マルチスレッド化するとメインウインドウに与える影響を少なくしつつバックグラウンドで処理しやすい。ここではATLを利用した環境で簡単にスレッドを生成できるクラス「CDnpThreadImpl」を作成した。
■使用例
#include "DnpThreadImpl.h"
///
///\brief
/// テスト用のスレッドクラス
///
class CTestThread : public CDnpThreadImpl
{
protected:
///
///\brief
/// スレッドメイン処理
///
/// CDnpThreadImpl::ThreadMain_() から呼ばれる
///ユーザーは呼び出さないこと
///
///\param pParam
/// CreateThread() で渡したユーザー定義のパラメータ
///
virtual DWORD ThreadMain(void* pParam)
{
ATLTRACE("■スレッド開始\n");
while(1)
{
//処理は無限ループ
::Sleep(1000);
// IsAbort() でスレッド中断を検出して関数から抜ける
if(IsAbort())
break;
}
ATLTRACE("■スレッド終了\n");
return 0;
}
};
以上のようにスレッド本体処理用のクラスをCDnpThreadImplから派生する形で作ったら、
CTestThread cThread; cThread.CreateThread(NULL);とするだけでワーカースレッドが生成して処理が実行される。処理を中断したい場合は
cThread.AbortThread();のようにする。
スレッドが完全に終了するまで待ちたい場合は
_cThread.DestroyThread(5000); //最大5秒待つのようにする。
■「DnpCriticalSection.h」の内容
#pragma once
#include "atlexcept.h"
/*!
* \brief
* クリティカルセクションクラス
*
* 例外を処理しているだけで、 CComAutoCriticalSection をそのまま利用。
*
*/
class CDnpCriticalSection
{
CComAutoCriticalSection _sec; //!< クリティカルセクション本体
public:
~CDnpCriticalSection()
{
HRESULT hr;
hr = Unlock();
ATLASSERT(SUCCEEDED(hr));
}
/*!
* \brief
* ロック
*
*/
HRESULT Lock(void)
{
HRESULT hr;
try
{
hr = _sec.Lock();
if(FAILED(hr))
return hr;
}
catch(CAtlException& e)
{
ATLTRACE("■例外発生(HRESULT=%08X)\n",e.m_hr);
}
return hr;
}
/*!
* \brief
* ロック解除
*
*/
HRESULT Unlock(void)
{
HRESULT hr;
try
{
hr = _sec.Unlock();
if(FAILED(hr))
return hr;
}
catch(CAtlException& e)
{
ATLTRACE("■例外発生(HRESULT=%08X)\n",e.m_hr);
}
return hr;
}
};
■「DnpThreadImpl.h」の内容
#pragma once
#include "DnpCriticalSection.h"
///
///\brief
/// スレッド実装用クラス
///
///■利用例
/*! \code
//
{
CTestThread _cThread;
_cThread.CreateThread(NULL);
_cThread.AbortThread();
_cThread.DestroyThread(1000);
}
//
class CTestThread : public CDnpThreadImpl
{
protected:
///
///\brief
/// スレッドメイン処理
///
/// CDnpThreadImpl::ThradMain_() から呼ばれる
///ユーザーは呼び出さないこと
///
///\param pParam
/// CreateThread() で渡したユーザー定義のパラメータ
///
virtual DWORD ThradMain(void* pParam)
{
ATLTRACE("■スレッド開始\n");
while(1)
{
::Sleep(1000);
// IsAbort() でスレッド中断を検出して関数から抜ける
if(IsAbort())
break;
}
ATLTRACE("■スレッド終了\n");
return 0;
}
};
///\endcode
*/
///
class CDnpThreadImpl
{
///
///\brief
/// スレッドパラメーター
///
///内部利用するのみなのでprivate構造体
///
struct THREADPARAM
{
CDnpThreadImpl* pThis; //!< このクラスのポインタ
void* pParam; //!< ユーザーが CreateThread() を読んだ時に指定されたパラメータ
};
///
///\attention
///以下のスレッド中断用フラグとクリティカルセクションは
///put_bAbort() と get_bAbort() 以外からはアクセスしないこと!
///
CDnpCriticalSection _sec_bAbort; //!< スレッド中断フラグ用クリティカルセクション
bool _bAbort; //!< スレッド中断用フラグ
protected:
DWORD _dwThreadID; //!< 生成したスレッドのID
HANDLE _hThread; //!< 生成したスレッドのハンドル
private:
///
///\brief
/// スレッド中断用フラグ設定用関数
///
///\param[in] bNew
///新しいフラグ。trueならスレッド中断開始、falseなら無視
///
///\retval true 設定成功
///\retval false 失敗
///
bool put_bAbort(bool bNew)
{
HRESULT hr;
hr = _sec_bAbort.Lock();
if(FAILED(hr))
return false;
_bAbort = bNew;
_sec_bAbort.Unlock();
return true;
}
///
///\brief
/// スレッド中断用フラグ取得用関数
///
///\param[out] bValue
///取得した中断フラグ。trueならスレッド中断処理中もしくは中断終了
///
///\retval true 取得成功
///\retval false 失敗
///
bool get_bAbort(bool& bValue)
{
HRESULT hr;
hr = _sec_bAbort.Lock();
if(FAILED(hr))
return false;
bValue = _bAbort;
_sec_bAbort.Unlock();
return true;
}
protected:
///
///\brief
/// スレッド中断中(もしくは中断終了した)かどうか
///
///\retval true 中断中
///\retval false それ以外
///
///生成したスレッド内で中断処理をする必要があるかどうかを
///チェックするのに利用する。
///
virtual bool IsAbort(void)
{
bool bValue;
if(get_bAbort(bValue))
return bValue;
//取得に失敗したときはabortフラグfalseとして処理
return false;
}
public:
CDnpThreadImpl()
{
_dwThreadID = 0;
_hThread = 0;
put_bAbort(false);
}
virtual ~CDnpThreadImpl()
{
////スレッドが破棄されていなければASSERT
//ATLASSERT(_dwThreadID == 0 && _hThread == 0);
DestroyThread(2000); //スレッド終了を最大2秒待つ
}
///
///\brief
/// スレッド中断処理開始用
///
///スレッドを中断したいときに呼び出す
///
///\retval true 設定成功
///\retval false 失敗
///
virtual bool AbortThread(void)
{
return put_bAbort(true);
}
///
///\brief
/// スレッドの終了を待つ
///
///生成したスレッドからは呼ばないこと
///
///\param[in] dwWait
/// 終了を待つ最大時間(単位:ミリ秒)\n
/// INFINITE なら無限に待つ
///
///\retval true スレッドは終了した
///\retval false スレッド終了待ちに失敗
///
bool WaitUntilThreadEnd(DWORD dwWait=INFINITE)
{
DWORD dwRet;
if(_hThread == NULL)
return true;
dwRet = ::WaitForSingleObject(_hThread,dwWait);
if(dwRet == WAIT_TIMEOUT)
return false;
return true;
}
///
///\brief
/// スレッド破壊処理
///
///スレッド中断フラグを立ててスレッドが終了するのを待つ
///
///生成したスレッドからは呼ばないこと
///
///\param[in] dwWait
/// 終了を待つ最大時間(単位:ミリ秒)\n
/// INFINITE なら無限に待つ
///
///\retval true スレッドは破壊された
///\retval false 失敗
///
bool DestroyThread(DWORD dwWait=INFINITE)
{
if(_hThread == NULL)
return true;
//スレッド強制終了フラグを立てる
AbortThread();
WaitUntilThreadEnd(dwWait);
if(IsThreadRunning() == false)
{
::CloseHandle(_hThread);
_hThread = NULL;
_dwThreadID = 0;
return true;
}
return false;
}
///
///\brief
/// スレッドが現在実行中かどうか
///
///生成したスレッドからは呼ばないこと
///
///\retval true 実行中
///\retval false 実行していない
///
bool IsThreadRunning(void) const
{
BOOL ret;
DWORD dwCode;
if(_hThread == NULL)
return false;
ret = ::GetExitCodeThread(_hThread,&dwCode);
if(ret == FALSE)
return false;
return (dwCode == STILL_ACTIVE) ? true : false;
}
///
///\brief
/// スレッド生成処理
///
///\param[in] pParam
/// スレッドに渡したいユーザー定義パラメーター。自由に利用していい
///
///\return
/// ゼロなら失敗。ゼロ以外なら成功
///
DWORD CreateThread(void* pParam)
{
ATLASSERT(_dwThreadID == 0 && _hThread == 0);
THREADPARAM* pData;
pData = new THREADPARAM;
pData->pParam = pParam;
pData->pThis = this;
put_bAbort(false);
_hThread = ::CreateThread(NULL,0,ThreadMain_,pData,0,&_dwThreadID);
if(_hThread == NULL)
{
delete pData;
return 0;
}
return _dwThreadID;
}
private:
///
///\brief
/// スレッドメイン関数
///
///APIの CreateThread から呼ばれる。それ以外からは利用されない。
///
///内部で仮想関数の ThreadMain() を呼びだしている
///
static DWORD WINAPI ThreadMain_(void* pParam)
{
THREADPARAM* pData = (THREADPARAM*) pParam;
CDnpThreadImpl* pThis = pData->pThis;
void* pUseParam = pData->pParam;
delete pParam;
return pThis->ThreadMain(pUseParam);
}
///
///\brief
/// スレッドメイン処理(仮想関数のため実装なし)
///
///\param pParam
/// CreateThread() の引数として渡されたユーザー定義のパラメータ
///
virtual DWORD ThreadMain(void* pParam) = 0;
};
