[MFC] Worker Thread 동적 관리기능 구현

반응형

Thread를 원하는 시간에만 동작시키려면? 특정 조건에서 멈추거나 종료하려면? Thread 동작 상태를 파악하려면? Thread 동적 관리 기능이 필요합니다. 이번 포스팅에서는 프로그램 개발단계부터 디버깅 용도로 유용하게 활용할 수 있는 Thread 관리기능(생성, 서스펜션, 디스트로이) 구현 방안을 예제 기반으로 설명해 보겠습니다. 이하 본문에서는 Thread를 태스크(TASK)라 지칭하도록 하겠습니다.

1.  동적 관리기능 요구사항

태스크 동적 관리기능의 구현에 앞서 반영할 요구사항은 다음과 같습니다.

  1. 프로그램 동작 상태에서 동적으로 태스크를 생성, 중지, 종료할 수 있어야 한다.
  2. 활용 가능한 태스크 목록을 관리하고 정보를 제공할 수 있어야 한다.
  3. 태스크 상태를 확인할 수 있어야 한다.
  4. 태스크 추가/삭제가 용이하도록 일관된 인터페이스를 제공해야 한다.

위 요구사항에 맞춘 결과물(GUI화면)을 먼저 확인해 보도록 하겠습니다.

Task 관리기능 사용 예

  • task list: 사용할 수 있는(프로그램에 탑재 되어있는 태스크) 태스크 목록을 보여줍니다. 이 예제에서는 "data_collector"라는 이름의 태스크 1개가 탑재되어 있습니다.
  • task status: 생성되어 있는 태스크를 나열하고, 생성시각, 태스크 타입, 태스크 ID, 태스크 상태, 태스크명을 보여줍니다.
  • task create -a <태스크명>: 태스크를 생성합니다.
  • task [suspend | destroy] -a <TID>: 태스크를 중지 또는 종료합니다.

2. 태스크 인터페이스 

모든 태스크들의 아버지라 할 수 있는 태스크 아빠 클래스(CTaskFather)를 구현하여 개별 태스크 클래스 구현시 태스크 아빠 클래스를 무조건 상속받도록 합니다. 또한 태스크 관리 클래스(CTaskMgr)를 구현하여 사용가능한 태스크 목록, 태스크 이름, 태스크 객체 관리 구조체 등을 관리하고 생성, 중지, 종료 등의 인터페이스를 구현합니다.

 

태스크 모듈에서 사용할 에러번호는 다음과 같이 정의하여 사용하기로 합니다.

#pragma once

/* ErrorNo for Task */
namespace TASK_ERR
{
	constexpr int ESUCCESS = 0;
	constexpr int EALREADY_CREATED = -1;
	constexpr int EBEGIN_THREAD_FAILURE = -2;
	constexpr int EHANDLE_NULLPTR = -3;
	constexpr int EPARAMETER_OUTOFRANGE = -4;
	constexpr int EOUTOF_RESOURCE = -5;
	constexpr int EDUMMY_FUNCTION = -6;	/* Not implemented function */
	constexpr int ETASK_ID_LOOKUP_FAILURE = -7;
}

2.1. CTaskFather

#pragma once

#include "task_errno.h"

class CTaskFather;

namespace TASK_FATHER
{
	constexpr int TASK_MSG_ESCAPE_LOOPER = (WM_APP)+1;

	typedef struct _LPVOID_PARAMS {
		LPVOID lpOpaque_1;
		LPVOID lpOpaque_2;
		LPVOID lpOpaque_3;
		LPVOID lpOpaque_4;
		LPVOID lpOpaque_5;
		_LPVOID_PARAMS() {
			ZeroMemory(this, sizeof(struct _LPVOID_PARAMS));
		}
	} TST_LPVOID_PARAMS, * PTST_LPVOID_PARAMS;	// LPVOID PARAMS TYPE for THREAD Creation

	typedef enum class enTaskStatus {
		UNKNOWN,
		RUNNING,
		SUSPENDED,
		SUSPENDED_ERR,	/* SUSPEND caused by Error */
		WAIT_REAP,		/* Escaped loop, waiting for Join() */
		MAX
	} TEN_TASK_STATUS;

	typedef struct _task_control_block_root {
		SRWLOCK srwlock;
		LPVOID ptheApp;
		CTaskFather* pTaskFather;
		TEN_TASK_STATUS enTaskStatus;
		AFX_THREADPROC pfnThreadProc;
		CWinThread* pThdLooper;
		HANDLE hEvent_Resume;
		HANDLE hEvent_Suspend;	/* Temporary usage as a creation event in the thread creation sequence */
		HANDLE hEvent_QuitThread;
		HANDLE hEvent_Exec;
		DWORD dwMsecInterval;			/* Timeout Milliseconds value for xxWaitForxxObjectsxx() */
		DWORD dwGetLastErrorValue;
		LPVOID lpOpaque_derived_1;	/* reserved for derived class */
		LPVOID lpOpaque_derived_2;	/* reserved for derived class */
		LPVOID lpOpaque_derived_3;	/* reserved for derived class */

		_task_control_block_root() {
			ZeroMemory(this, sizeof(struct _task_control_block_root));
			InitializeSRWLock(&srwlock);
		}
	} TST_TCB_ROOT, *PTST_TCB_ROOT;
}

class CTaskFather
{
protected:	/* Prevent create CTaskFather Without Derived child class */
	CTaskFather();
public:
	virtual ~CTaskFather();

public:
	virtual int iRun(void) { return TASK_ERR::EDUMMY_FUNCTION; };
	virtual int iCreate(void);
	virtual void vSuspend(void) { SetEvent(_m_stTcbRoot.hEvent_Suspend); };
	virtual void vResume(void) { SetEvent(_m_stTcbRoot.hEvent_Resume); };
	virtual int iDestroy(void);

	DWORD dwGetTaskId(void);
	inline TASK_FATHER::TEN_TASK_STATUS enGetTaskStatus(void) { return _m_stTcbRoot.enTaskStatus; }

protected:
	TASK_FATHER::TST_TCB_ROOT _m_stTcbRoot;
};

 

#include "pch.h"
#include "CTaskFather.h"

#include "Noyecube.h"

#include "util/CAutolock.h"

CTaskFather::CTaskFather() 
{
	CString cstrEventResume;
	CString cstrEventSuspend;
	CString cstrEventQuitThread;
	CString cstrEventExec;

	CAutoScopeSRWLOCKExclusive lock(&(_m_stTcbRoot.srwlock));

	_m_stTcbRoot.enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::UNKNOWN;
	_m_stTcbRoot.pThdLooper = nullptr;

	cstrEventResume.Format(_T("CTaskFather_hEvent_Resume_%p"), this);
	cstrEventSuspend.Format(_T("CTaskFather_hEvent_Suspend_%p"), this);
	cstrEventQuitThread.Format(_T("CTaskFather_hEvent_QuitThread_%p"), this);
	cstrEventExec.Format(_T("CTaskFather_hEvent_Exec_%p"), this);

	_m_stTcbRoot.hEvent_Resume = CreateEvent(NULL, CREATE_EVENT_MANUAL_RESET, FALSE, cstrEventResume);
	_m_stTcbRoot.hEvent_Suspend = CreateEvent(NULL, CREATE_EVENT_MANUAL_RESET, FALSE, cstrEventSuspend);
	_m_stTcbRoot.hEvent_QuitThread = CreateEvent(NULL, CREATE_EVENT_MANUAL_RESET, FALSE, cstrEventQuitThread);
	_m_stTcbRoot.hEvent_Exec = CreateEvent(NULL, CREATE_EVENT_MANUAL_RESET, FALSE, cstrEventExec);

	if ((nullptr == _m_stTcbRoot.hEvent_Resume) || (nullptr == _m_stTcbRoot.hEvent_Suspend) ||
		(nullptr == _m_stTcbRoot.hEvent_Exec) || (nullptr == _m_stTcbRoot.hEvent_QuitThread)) {
		AfxMessageBox(_T("Fail:NoyeCube:CTaskFather(TID:%d).CreateEvent"), GetCurrentThreadId());
		PostQuitMessage(0);
	}
};

CTaskFather::~CTaskFather()
{
	DPRINTF("ENTER\n");
	if (_m_stTcbRoot.pThdLooper) {
		DPRINTF("[DBG]  ==> SetEvent(_m_stTcb.hEvent_QuitThread);...\n");
		SetEvent(_m_stTcbRoot.hEvent_QuitThread);
		/*_m_stTcb.pThdLooper->PostThreadMessage(TASK_FATHER::TASK_MSG_ESCAPE_LOOPER, 0, 0);*/
		DPRINTF("[DBG]  ==> Before WaitForSingleObject(_m_stTcb.pThdLooper->m_hThread, INFINITE);...\n");
		WaitForSingleObject(_m_stTcbRoot.pThdLooper->m_hThread, INFINITE);
		DPRINTF("[DBG]  ==> After WaitForSingleObject(_m_stTcb.pThdLooper->m_hThread, INFINITE);...\n");
		_m_stTcbRoot.pThdLooper = nullptr;
	}

	_m_stTcbRoot.enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::UNKNOWN;
	_m_stTcbRoot.dwGetLastErrorValue = 0;

	if (_m_stTcbRoot.hEvent_Exec) {
		CloseHandle(_m_stTcbRoot.hEvent_Exec);
		_m_stTcbRoot.hEvent_Exec = nullptr;
	}
	if (_m_stTcbRoot.hEvent_QuitThread) {
		CloseHandle(_m_stTcbRoot.hEvent_QuitThread);
		_m_stTcbRoot.hEvent_QuitThread = nullptr;
	}
	if (_m_stTcbRoot.hEvent_Resume) {
		CloseHandle(_m_stTcbRoot.hEvent_Resume);
		_m_stTcbRoot.hEvent_Resume = nullptr;
	}
	if (_m_stTcbRoot.hEvent_Suspend) {
		CloseHandle(_m_stTcbRoot.hEvent_Suspend);
		_m_stTcbRoot.hEvent_Suspend = nullptr;
	}

	DPRINTF("LEAVE\n");
}

int CTaskFather::iCreate(void)
{
	CAutoScopeSRWLOCKExclusive lock(&(_m_stTcbRoot.srwlock));

	if (nullptr != _m_stTcbRoot.pThdLooper) {
		return TASK_ERR::EALREADY_CREATED;
	}

	if (nullptr == _m_stTcbRoot.pfnThreadProc) {
		return TASK_ERR::EHANDLE_NULLPTR;
	}

	if ((nullptr == _m_stTcbRoot.hEvent_Resume) || (nullptr == _m_stTcbRoot.hEvent_Suspend)) {
		return TASK_ERR::EHANDLE_NULLPTR;
	}

	_m_stTcbRoot.ptheApp = (LPVOID)(&theApp);
	_m_stTcbRoot.pTaskFather = this;

	DPRINTF("[DEBUG] pTaskFather (%p)\n", this);

	ResetEvent(_m_stTcbRoot.hEvent_Resume);
	ResetEvent(_m_stTcbRoot.hEvent_Suspend);
	ResetEvent(_m_stTcbRoot.hEvent_QuitThread);
	ResetEvent(_m_stTcbRoot.hEvent_Exec);

	_m_stTcbRoot.pThdLooper = AfxBeginThread(_m_stTcbRoot.pfnThreadProc, (LPVOID)&_m_stTcbRoot);
	if (nullptr == _m_stTcbRoot.pThdLooper) {
		return TASK_ERR::EBEGIN_THREAD_FAILURE;
	}

	if (nullptr != _m_stTcbRoot.hEvent_Suspend) {	/* Temporary usage as a creation event in the thread creation sequence */
		DPRINTF("WAITING ThdLooper Creation...");
		WaitForSingleObject(_m_stTcbRoot.hEvent_Suspend, INFINITE);
		ResetEvent(_m_stTcbRoot.hEvent_Suspend);
		DPRINTF("Completed.\n");
	}

	SetEvent(_m_stTcbRoot.hEvent_Resume);	/* Signal to Entering ThdLooper Thread Loop*/

	return TASK_ERR::ESUCCESS;
};

int CTaskFather::iDestroy(void)
{
	CAutoScopeSRWLOCKExclusive lock(&(_m_stTcbRoot.srwlock));

	if (nullptr == _m_stTcbRoot.pThdLooper) {
		return TASK_ERR::EHANDLE_NULLPTR;
	}

	DPRINTF("[DBG]  ==> SetEvent(_m_stTcb.hEvent_QuitThread);...\n");
	SetEvent(_m_stTcbRoot.hEvent_QuitThread);
	/*_m_stTcb.pThdLooper->PostThreadMessage(TASK_FATHER::TASK_MSG_ESCAPE_LOOPER, 0, 0);*/
	DPRINTF("[DBG]  ==> Before WaitForSingleObject(_m_stTcb.pThdLooper->m_hThread, INFINITE);...\n");
	WaitForSingleObject(_m_stTcbRoot.pThdLooper->m_hThread, INFINITE);
	DPRINTF("[DBG]  ==> After WaitForSingleObject(_m_stTcb.pThdLooper->m_hThread, INFINITE);...\n");
	
	_m_stTcbRoot.pThdLooper = nullptr;
	_m_stTcbRoot.enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::UNKNOWN;
	_m_stTcbRoot.dwGetLastErrorValue = 0;

	return TASK_ERR::ESUCCESS;
}

DWORD CTaskFather::dwGetTaskId(void)
{
	CAutoScopeSRWLOCKShared lock(&(_m_stTcbRoot.srwlock));

	if (_m_stTcbRoot.pThdLooper)
		return _m_stTcbRoot.pThdLooper->m_nThreadID;
	else
		return 0;
}

 

2.2. CTaskMgr

#pragma once

#include "CTaskFather.h"

#include "task_errno.h"

#include "DataCollector/CThdDataCollector.h"


#define N_MAX_TASK_OBJECT	(16)

namespace TASK_MGR 
{
	typedef enum class _enTaskCategory {
		DATA_COLLECTOR,
		MAX
	} TEN_TASK_CAT;

	typedef struct _task_property {
		const TEN_TASK_CAT enTaskCat;
		TCHAR tchTaskName[64];
	} TST_TASK_PROP, *PTST_TASK_PROP;

	typedef struct _task_object_entity {
		BOOL boUsed;
		TEN_TASK_CAT enTaskCat;
		CTaskFather* pTaskFather;
		DWORD dwLooperTid;
		SYSTEMTIME tmTagCreated;
		//SYSTEMTIME tmTagCompleted;
		ULONGLONG ullTickTagCreated;
		_task_object_entity() {
			ZeroMemory(this, sizeof(_task_object_entity));
		}

		void EraseData(void) {
			ZeroMemory(this, sizeof(_task_object_entity));
		}
	} TST_TASK_OBJECT, *PTST_TASK_OBJECT;

	typedef struct _task_object_entity_pool {
		SRWLOCK srwlock;
		TST_TASK_OBJECT _m_astTaskObj[N_MAX_TASK_OBJECT];
		_task_object_entity_pool() {
			InitializeSRWLock(&srwlock);
		}
	} TST_TASK_OBJECT_POOL, *PTST_TASK_OBJECT_POOL;
}

using namespace TASK_MGR;
class CTaskMgr
{
public:
	CTaskMgr();
	~CTaskMgr();

public:
	int CreateTask(TEN_TASK_CAT enTaskCat);
	int CreateTask(LPCTSTR lpTaskName);
	int DestroyTask(TEN_TASK_CAT enTaskCat);
	int DestroyTask(LPCTSTR lpTaskName);

	int DestroyTask(DWORD dwTaskId);
	int SuspendTask(DWORD dwTaskId);
	int ResumeTask(DWORD dwTaskId);

public:
	PTST_TASK_OBJECT_POOL pRefer_PTST_TASK_OBJECT_POOL(void) { return (&_m_stTaskObjPool); }
	PTST_TASK_PROP pRefer_PTST_TASK_PROP(void) { return _m_astTaskProp; }
private:
	int _iAcquireSlot_TaskObjPool(void);

private:
	//CThdDataCollector* _m_pDataCollector;

	TST_TASK_PROP _m_astTaskProp[static_cast<unsigned int>(TEN_TASK_CAT::MAX)] = {
		{ TEN_TASK_CAT::DATA_COLLECTOR, _T("data_collector")}
	};

	TST_TASK_OBJECT_POOL _m_stTaskObjPool;

};
#include "pch.h"
#include "CTaskMgr.h"


CTaskMgr::CTaskMgr()
{

}

CTaskMgr::~CTaskMgr()
{

}

int CTaskMgr::_iAcquireSlot_TaskObjPool(void)
{
	int iSlot = TASK_ERR::EOUTOF_RESOURCE;

	AcquireSRWLockExclusive(&(_m_stTaskObjPool.srwlock));
		for (int i = 0; i < N_MAX_TASK_OBJECT; i++) {
			if (FALSE == _m_stTaskObjPool._m_astTaskObj[i].boUsed) {
				_m_stTaskObjPool._m_astTaskObj[i].boUsed = TRUE;
				iSlot = i;
				break;
			}
		}
	ReleaseSRWLockExclusive(&(_m_stTaskObjPool.srwlock));

	return iSlot;
}

int CTaskMgr::CreateTask(TEN_TASK_CAT enTaskCat)
{
	int iPoolIndex;
	int iRet = TASK_ERR::EDUMMY_FUNCTION;

	if (TEN_TASK_CAT::MAX == enTaskCat) {
		return TASK_ERR::EPARAMETER_OUTOFRANGE;
	}

	iPoolIndex = this->_iAcquireSlot_TaskObjPool();
	if (iPoolIndex < 0) {
		return TASK_ERR::EOUTOF_RESOURCE;
	}

	PTST_TASK_OBJECT p = &(_m_stTaskObjPool._m_astTaskObj[iPoolIndex]);

	switch (enTaskCat) {
	case TEN_TASK_CAT::DATA_COLLECTOR:
		p->enTaskCat = TEN_TASK_CAT::DATA_COLLECTOR;
		p->ullTickTagCreated = GetTickCount64();
		GetLocalTime(&(p->tmTagCreated));

		p->pTaskFather = new CThdDataCollector();

		iRet = p->pTaskFather->iCreate();
		if (iRet < 0) {
			DPRINTF("[ERROR] CreateTask (%s) Failure(%d).\n", _m_astTaskProp[static_cast<UINT>(enTaskCat)].tchTaskName, iRet);
		}
		else {
			p->dwLooperTid = p->pTaskFather->dwGetTaskId();
			DPRINTF("[INFO] Task (%s) (tid:%u) created\n", _m_astTaskProp[static_cast<UINT>(enTaskCat)].tchTaskName, p->dwLooperTid);
		}
		break;
	default:
		;
	}

	return iRet;
}

int CTaskMgr::CreateTask(LPCTSTR lpTaskName)
{
	TEN_TASK_CAT enCat = TEN_TASK_CAT::MAX;
	for (int i = 0; i < sizeof(_m_astTaskProp) / sizeof(_m_astTaskProp[0]); i++) {
		if (0 == _tcsncmp(_m_astTaskProp[i].tchTaskName, lpTaskName, _tcslen(_m_astTaskProp[i].tchTaskName))) {
			enCat = _m_astTaskProp[i].enTaskCat;
			break;
		}
	}

	return this->CreateTask(enCat);
}

int CTaskMgr::DestroyTask(TEN_TASK_CAT enTaskCat)
{
	return TASK_ERR::EDUMMY_FUNCTION;
}

int CTaskMgr::DestroyTask(LPCTSTR lpTaskName)
{
	return TASK_ERR::EDUMMY_FUNCTION;
}

int CTaskMgr::DestroyTask(DWORD dwTaskId)
{
	int iPoolIdx = -1;

	AcquireSRWLockShared(&(_m_stTaskObjPool.srwlock));
	for (int i = 0; i < N_MAX_TASK_OBJECT; i++) {
		if (dwTaskId == _m_stTaskObjPool._m_astTaskObj[i].dwLooperTid) {
			iPoolIdx = i;
		}
	}
	ReleaseSRWLockShared(&(_m_stTaskObjPool.srwlock));

	if (iPoolIdx < 0) {
		return TASK_ERR::ETASK_ID_LOOKUP_FAILURE;
	}

	AcquireSRWLockExclusive(&(_m_stTaskObjPool.srwlock));
	_m_stTaskObjPool._m_astTaskObj[iPoolIdx].pTaskFather->iDestroy();
	delete _m_stTaskObjPool._m_astTaskObj[iPoolIdx].pTaskFather;
	_m_stTaskObjPool._m_astTaskObj[iPoolIdx].EraseData();
	ReleaseSRWLockExclusive(&(_m_stTaskObjPool.srwlock));

	return TASK_ERR::ESUCCESS;
}

int CTaskMgr::SuspendTask(DWORD dwTaskId)
{
	int iPoolIdx = -1;

	AcquireSRWLockShared(&(_m_stTaskObjPool.srwlock));
	for (int i = 0; i < N_MAX_TASK_OBJECT; i++) {
		if (dwTaskId == _m_stTaskObjPool._m_astTaskObj[i].dwLooperTid) {
			iPoolIdx = i;
		}
	}
	ReleaseSRWLockShared(&(_m_stTaskObjPool.srwlock));

	if (iPoolIdx < 0) {
		return TASK_ERR::ETASK_ID_LOOKUP_FAILURE;
	}

	_m_stTaskObjPool._m_astTaskObj[iPoolIdx].pTaskFather->vSuspend();

	return TASK_ERR::ESUCCESS;
}

int CTaskMgr::ResumeTask(DWORD dwTaskId)
{
	int iPoolIdx = -1;

	AcquireSRWLockShared(&(_m_stTaskObjPool.srwlock));
	for (int i = 0; i < N_MAX_TASK_OBJECT; i++) {
		if (dwTaskId == _m_stTaskObjPool._m_astTaskObj[i].dwLooperTid) {
			iPoolIdx = i;
		}
	}
	ReleaseSRWLockShared(&(_m_stTaskObjPool.srwlock));

	if (iPoolIdx < 0) {
		return TASK_ERR::ETASK_ID_LOOKUP_FAILURE;
	}

	_m_stTaskObjPool._m_astTaskObj[iPoolIdx].pTaskFather->vResume();

	return TASK_ERR::ESUCCESS;
}

 

3. 샘플 태스크

데이터 수집을 수행하는 역할의 태스크("data_collector")를 위 인터페이스를 적용하여 구현해 보았습니다.

아무 내용도 없는 더미 태스크라고 보면 됩니다. CTaskFater를 상속받아 virtual function을 override 하였기 때문에 CTaskFather의 포인터로 제어 가능합니다. Loop 내에서 MsgWaitForMultipleObjectsEx를 적용하였기 때문에 중지, 종료 이벤트뿐 아니라 다양한 MSG를 수신할 수 있습니다. 또한 State-Machine 방식으로 동작하도록 사전에 각 State를 정의하였으며 Main-State가 있고 각 Main-State에 대한 Sub-State가 있습니다.

#pragma once

#include "task/CTaskFather.h"

namespace TASK_DATA_COLLECTOR
{
	typedef enum class _enStateMain {
		UNKNOWN,
		INIT,		/* Check requirement */
		REAL_DATA_RECV,
		HISTORICAL_DATA_COLLECT,
		REAL_DATA_CORRECTION,
		IDLE_WAIT,
		MAX
	} TEN_STAT_MAIN;

	typedef enum class _enStateSub_INIT {
		ENTRY,
		MAX
	} TEN_STAT_SUB_INIT;

	typedef enum class _enStateSub_REAL_DATA_RECV {
		ENTRY,
		MAX
	} TEN_STAT_SUB_REAL_D_RCV;

	typedef enum class _enStateSub_HISTORICAL_DATA_COLLECT {
		ENTRY,
		MAX
	} TEN_STAT_SUB_HIST_D_COL;

	typedef enum class _enStateSub_REAL_DATA_CORRECTION {
		ENTRY,
		MAX
	} TEN_STAT_SUB_REAL_D_COR;

	typedef struct _task_control_block {
		SRWLOCK srwlock;

		BOOL boNoReturn;

		TEN_STAT_MAIN enStat_Main;
		TEN_STAT_MAIN enStatOld_Main;

		TEN_STAT_SUB_INIT enSub_INIT;
		TEN_STAT_SUB_INIT enSubOld_INIT;

		TEN_STAT_SUB_REAL_D_RCV enSub_REAL_D_RCV;
		TEN_STAT_SUB_REAL_D_RCV enSubOld_REAL_D_RCV;

		TEN_STAT_SUB_HIST_D_COL enSub_HIST_D_COL;
		TEN_STAT_SUB_HIST_D_COL enSubOld_HIST_D_COL;

		TEN_STAT_SUB_REAL_D_COR enSub_REAL_D_COR;
		TEN_STAT_SUB_REAL_D_COR enSubOld_REAL_D_COR;

		_task_control_block() {
			ZeroMemory(this, sizeof(struct _task_control_block));
			InitializeSRWLock(&srwlock);
		}
	} TST_TCB, * PTST_TCB;

}

using namespace TASK_DATA_COLLECTOR;

class CThdDataCollector : public CTaskFather
{
public:
	CThdDataCollector();
	~CThdDataCollector();

	int iRun(void) override;

	int iCreate(void) override;
	void vSuspend(void) override;
	void vResume(void) override;
	int iDestroy(void) override;
	static UINT ThdLooper(LPVOID pParam);

private:
	TST_TCB _m_stTcb;

private:
	void vRun_UNKNOWN(void);
	void vRun_INIT(void);
	void vRun_REAL_D_RCV(void);
	void vRun_HIST_D_COL(void);
	void vRun_REAL_D_COR(void);


};
#include "pch.h"
#include "CThdDataCollector.h"

#include "Noyecube.h"

#include "util/CAutolock.h"

using namespace TASK_DATA_COLLECTOR;
using namespace TASK_FATHER;
using namespace TASK_ERR;

CThdDataCollector::CThdDataCollector()
{
	_m_stTcbRoot.pfnThreadProc = this->ThdLooper;

	_m_stTcbRoot.lpOpaque_derived_1 = (LPVOID)(this);
	_m_stTcbRoot.lpOpaque_derived_2 = (LPVOID)(theApp.m_pXingMgr);

}

CThdDataCollector::~CThdDataCollector()
{

}

int CThdDataCollector::iCreate(void)	// override
{
	return CTaskFather::iCreate();
}

void CThdDataCollector::vSuspend(void)	// override
{
	CTaskFather::vSuspend();
}

void CThdDataCollector::vResume(void)	// override
{
	CTaskFather::vResume();
}

int CThdDataCollector::iDestroy(void)	// override
{
	return CTaskFather::iDestroy();
}

int CThdDataCollector::iRun(void)	// override
{

	do {

		_m_stTcb.boNoReturn = FALSE;

		switch (_m_stTcb.enStat_Main) {
		case TEN_STAT_MAIN::UNKNOWN:
			this->vRun_UNKNOWN();
			break;
		case TEN_STAT_MAIN::INIT:
			this->vRun_INIT();
			break;
		case TEN_STAT_MAIN::REAL_DATA_RECV:
			this->vRun_REAL_D_RCV();
			break;
		case TEN_STAT_MAIN::HISTORICAL_DATA_COLLECT:
			this->vRun_HIST_D_COL();
			break;
		case TEN_STAT_MAIN::REAL_DATA_CORRECTION:
			this->vRun_REAL_D_COR();
			break;
		case TEN_STAT_MAIN::IDLE_WAIT:
			;
			break;
		default:
			;
		}

		if (TRUE == _m_stTcb.boNoReturn) {
			continue;
		}

		break;

	} while (TRUE);

	return TASK_ERR::EDUMMY_FUNCTION;
}

UINT CThdDataCollector::ThdLooper(LPVOID pParam)
{
	PTST_TCB_ROOT pTcb = (PTST_TCB_ROOT)pParam;
	
	SetEvent(pTcb->hEvent_Suspend);

	WaitForSingleObject(pTcb->hEvent_Resume, INFINITE);
	ResetEvent(pTcb->hEvent_Resume);

	DPRINTF("ENTER Thread LOOP\n");

	DWORD dwWaitRet;
	MSG msg;
	constexpr DWORD dwNCOUNT = 3;
	HANDLE arrhEvents[dwNCOUNT] = {
		pTcb->hEvent_Suspend,
		pTcb->hEvent_Exec,
		pTcb->hEvent_QuitThread
	};
	
	HANDLE arrhEventsSuspended[2] = {
		pTcb->hEvent_Resume,
		pTcb->hEvent_QuitThread
	};
	pTcb->enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::RUNNING;

	pTcb->dwMsecInterval = 1000; /////////////// TSET VALUE !!!!! 
	while (TRUE) {
		dwWaitRet = MsgWaitForMultipleObjectsEx(dwNCOUNT, arrhEvents, pTcb->dwMsecInterval, QS_POSTMESSAGE, MWMO_INPUTAVAILABLE);

		if (WAIT_TIMEOUT == dwWaitRet) {	/* Expired pTcb->dwMsecInterval */
			DPRINTF("Tid(%d) Father(%p) hEvent(%p) Expired!! (%d)\n", GetCurrentThreadId(), pTcb->pTaskFather, 
				pTcb->hEvent_Suspend, pTcb->dwMsecInterval);
		}
		else if ((WAIT_OBJECT_0 + 1) == dwWaitRet) {	/* hEvent_Exec */

		}
		else if ((WAIT_OBJECT_0 + dwNCOUNT) == dwWaitRet) {	/* QS_POSTMESSAGE for PeekMessage() */

		}
		else if ((WAIT_OBJECT_0 + 0) == dwWaitRet) {	/* hEvent_Suspend */
			DPRINTF("Before Suspend\n");
			pTcb->enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::SUSPENDED;
			dwWaitRet = WaitForMultipleObjects(2, arrhEventsSuspended, FALSE, INFINITE);
			if ((WAIT_OBJECT_0 + 0) == dwWaitRet) {	/* hEvent_Resume */
				pTcb->enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::RUNNING;
				DPRINTF("Escape Suspend\n");
				ResetEvent(pTcb->hEvent_Suspend);
				ResetEvent(pTcb->hEvent_Resume);
			}
			else if ((WAIT_OBJECT_0 + 1) == dwWaitRet) {	/* hEvent_QuitThread */
				pTcb->enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::WAIT_REAP;
				break;
			} 
			else if (WAIT_FAILED == dwWaitRet) {

			}
			else {
				;
			}

		}
		else if ((WAIT_OBJECT_0 + 2) == dwWaitRet) {	/* hEvent_QuitThread */
			pTcb->enTaskStatus = TASK_FATHER::TEN_TASK_STATUS::WAIT_REAP;
			break;
		}
//		else if ((WAIT_OBJECT_0 + 0) == dwWaitRet) {	/* hEvent_Resume */


//		}
		else if (WAIT_FAILED == dwWaitRet) {
			DPRINTF("[ERROR] WAIT_FAILED with ErrorNo(%d)\n", GetLastError());
		}
		else {
			;
		}

		pTcb->pTaskFather->iRun();
	}

	/* TODO: Terminate Working Job */

	return 0;
}

 

4. 진단(Diagnostics) 모듈과의 연결

사용자에게 명령줄을 통한 입력을 받아 태스크를 제어하기 위한 간략 설명입니다.

GUI 화면을 하나 만들어서 ListBox와 EditBox를 배치하여 ListBox에는 결과물 출력, EditBox는 명령어 입력을 받도록 합니다. 이 창에서는 가장 첫 단어만 파싱 및 나머지 파라미터를 관련 구조체에 담아 해당 명령 처리 모듈로 넘겨주는 역할을 합니다. 파라미터는 숫자타입 파라미터는 -0, -1, -2,... 뒤에 값을 넣고 문자열타입 파라미터는 -a, -b, -c,... 뒤에 문자타입 파라미터를 입력합니다.

Diagnostics Dialog의 "task" 명령어 처리 부분

핵심은 ProcessCommandLine이며 코드는 다음과 같습니다.

#include <regex>
BOOL CDlgLocalDiagConsole::ProcessCommandLine(int argc, TCHAR* argv[], LPVOID lpParam)
{
	PTST_DIAG_REQUEST_ITEM p = (PTST_DIAG_REQUEST_ITEM)lpParam;
	std::regex re("[-+]?[0-9]+");
#if 0
	for (int i = 0; i < argc; i++)
	{
		m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
			_T("\targv[%d]=<%s>\n"), i, argv[i]);
	}
#endif
	if (nullptr == p) {
		return FALSE;
	}

	int c;
	optind = 1;		// this does not normally have to be done, but in
	// this app we may be calling getopt again

// In the following loop you would set/unset any global command 
// line flags and option arguments (usually in the CWinApp object) 
// as each option was found in the command line.  
//
// Here you could also enforce any required order in the options -
// for example, if option 'b' is seen, then option 'a' must be given
// first.  However, this is unusual and probably not a good idea
// in most apps.
//
// In general it is probably best to let ProcessCommandLine's caller
// sort out the command line arguments that were used, and whether 
// they are consistent.  In ProcessCommandLine, you want to save the
// options and the arguments, doing any conversion (atoi, etc.) that
// is necessary.
//
// Note in the optstring there are colons following the option letters
// (d and e) that take arguments.  Also note that option letters are 
// case sensitive.
//
// Normally you would have a case statement for each option letter.
// In this demo app, the case statement for option 'f' has been
// omitted on purpose, to show what will happen.

	while ((c = getopt(argc, argv, _T("0:1:2:3:4:5:6:7:a:b:c:d:e:f:g:h:"))) != EOF)
	{
		switch (c)
		{
		case _T('0'):	/* long [0] */
		case _T('1'):	/* long [1] */
		case _T('2'):	/* long [2] */
		case _T('3'):	/* long [3] */
		case _T('4'):	/* long [4] */
		case _T('5'):	/* long [5] */
		case _T('6'):	/* long [6] */
		case _T('7'):	/* long [7] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption a with value '%s'\n"), optarg);
			if (regex_match(optarg, re)) {
				//if (TRUE) {
				p->lOpaque[c - '0'] = _ttoi(optarg);
				p->bolOpaque[c - '0'] = TRUE;
				//DPRINTF("Regex Match True _ttoi(%s)->(%d)\n", optarg, p->lOpaque[c-'0']);
			}
			else {
				DPRINTF("Regex Match False _ttoi(%s)->(%d)\n", optarg, p->lOpaque[c - '0']);
				m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::Yellow, 0, _T("Regex Match False _ttoi(%s)->(%d)\n"), optarg, p->lOpaque[c - '0']);
				return FALSE;
			}

			break;
		case _T('a'):	/* TCHAR[[0] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption a with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[0], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[0] = TRUE;
			break;
		case _T('b'):	/* TCHAR [1] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption b with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[1], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[1] = TRUE;
			break;
		case _T('c'):	/* TCHAR [2] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption c with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[2], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[2] = TRUE;
			break;
		case _T('d'):	/* TCHAR [3] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption d with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[3], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[3] = TRUE;
			break;
		case _T('e'):	/* TCHAR [4] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption e with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[4], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[4] = TRUE;
			break;
		case _T('f'):	/* TCHAR [5] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption f with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[5], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[5] = TRUE;
			break;
		case _T('g'):	/* TCHAR [6] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption g with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[6], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[6] = TRUE;
			break;
		case _T('h'):	/* TCHAR [7] */
			m_ctrXListBoxConsole.Printf(CXListBox::Black, CXListBox::White, 0,
				_T("\toption h with value '%s'\n"), optarg);
			_tcsncpy_s(p->tchOpaque[7], _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]), optarg, _countof(TST_DIAG_REQUEST_ITEM::tchOpaque[0]) - 1);
			p->botchOpaque[7] = TRUE;
			break;
		case _T('?'):		// illegal option - i.e., an option that was
			// not specified in the optstring

// Note: you may choose to ignore illegal options
#if 0	/* IGNORE */
			m_ctrListBoxConsole.Printf(CXListBox::Red, CXListBox::White, 0,
				_T("\tERROR:  illegal option %s\n"), argv[optind - 1]);
			{
				CString str;
				str.LoadString(AFX_IDS_APP_TITLE);
				CString msg;
				msg = _T("Usage:  " + str + " -a -b -c -C -d AAA -e NNN -f");
				AfxMessageBox(msg);
			}
			return FALSE;
#endif
			break;

		default:		// legal option - it was specified in optstring,
			// but it had no "case" handler 

// Note: you may choose not to make this an error

			m_ctrXListBoxConsole.Printf(CXListBox::Red, CXListBox::White, 0,
				_T("\tWARNING:  no handler for option %c\n"), c);
			return FALSE;
			break;
		}
	}

	if (optind < argc)
	{
		CString strArgs;
		strArgs = _T("");

		// In this loop you would save any extra arguments (e.g., filenames).
		while (optind < argc)
		{
			if (strArgs.IsEmpty())
				strArgs = _T("\tAdditional non-option arguments: ");
			strArgs += _T("<");
			strArgs += argv[optind];
			strArgs += _T("> ");
			optind++;
		}

		if (!strArgs.IsEmpty())
		{
			m_ctrXListBoxConsole.AddLine(CXListBox::Black, CXListBox::White, (LPCTSTR)strArgs);
		}
	}

	// all options processed, return success

	return TRUE;
}

 

ProcessCommandLine에서 파싱 한 파라미터들을 담아 Diagnostics 모듈로 메시지 전달을 합니다.

Diagnostics 모듈의 태스크 명령어 처리 부분 코드는 다음과 같습니다.

void CDiagnostics::vExec_LocalDiagTask(PTST_DIAG_REQUEST_ITEM p)
{
	if (nullptr == p) {
		return;
	}

	DPRINTF("Cmd: %s\n", p->tchDiagCmd);

	if (0 == _tcsncmp(_T("status"), p->tchDiagCmd, _tcsclen(_T("status")))) {
		DPRINTF("status\n");
		CString cstrTemp;
		PTST_TASK_OBJECT_POOL pObj = theApp.m_pTaskMgr->pRefer_PTST_TASK_OBJECT_POOL();
		PTST_TASK_PROP pList = theApp.m_pTaskMgr->pRefer_PTST_TASK_PROP();

		AcquireSRWLockShared(&(pObj->srwlock));
		for (int i = 0; i < N_MAX_TASK_OBJECT; i++) {
			if (TRUE == pObj->_m_astTaskObj[i].boUsed) {
				cstrTemp.Format(_T("[%d][%02d-%02d-%02d %02d:%02d:%02d] TASK[%d] TID[%d] "),
					i, pObj->_m_astTaskObj[i].tmTagCreated.wYear, pObj->_m_astTaskObj[i].tmTagCreated.wMonth,
					pObj->_m_astTaskObj[i].tmTagCreated.wDay, pObj->_m_astTaskObj[i].tmTagCreated.wHour,
					pObj->_m_astTaskObj[i].tmTagCreated.wMinute, pObj->_m_astTaskObj[i].tmTagCreated.wSecond,
					pObj->_m_astTaskObj[i].enTaskCat, pObj->_m_astTaskObj[i].dwLooperTid);

				switch (pObj->_m_astTaskObj[i].pTaskFather->enGetTaskStatus()) {
				case TASK_FATHER::TEN_TASK_STATUS::RUNNING:
					cstrTemp.AppendFormat(_T("STAT[RUNNING]"));
					break;
				case TASK_FATHER::TEN_TASK_STATUS::SUSPENDED:
					cstrTemp.AppendFormat(_T("STAT[SUSPENDED]"));
					break;
				default:
					cstrTemp.AppendFormat(_T("STAT[UNKNOWN]"));
				}
				
				cstrTemp.AppendFormat(_T("[%s]"), pList[static_cast<UINT>(pObj->_m_astTaskObj[i].enTaskCat)].tchTaskName);

				if (p->pXListBox) {
					p->pXListBox->Printf(CXListBox::Black, CXListBox::White, 0, cstrTemp);
				}
				else {
					DPRINTF("%s\n", (LPCTSTR)cstrTemp);
				}
			}
		}
		ReleaseSRWLockShared(&(pObj->srwlock));
	}
	else if (0 == _tcsncmp(_T("list"), p->tchDiagCmd, _tcsclen(_T("list")))) {
		PTST_TASK_PROP pList = theApp.m_pTaskMgr->pRefer_PTST_TASK_PROP();
		for (int i = 0; i < static_cast<unsigned int>(TEN_TASK_CAT::MAX); i++) {
			if (p->pXListBox) {
				p->pXListBox->Printf(CXListBox::Black, CXListBox::White, 0, _T("TEN_TASK_CAT[%d] %s"), pList[i].enTaskCat, pList[i].tchTaskName);
			}
			else {
				DPRINTF("TEN_TASK_CAT[%d] (%s)\n", pList[i].enTaskCat, pList[i].tchTaskName);
			}
		}
	}
	else if (0 == _tcsncmp(_T("resume"), p->tchDiagCmd, _tcsclen(_T("resume")))) {
		DPRINTF("resume\n");
		theApp.m_pTaskMgr->ResumeTask(_ttoi(p->tchOpaque[0]));
	}
	else if (0 == _tcsncmp(_T("suspend"), p->tchDiagCmd, _tcsclen(_T("suspend")))) {
		DPRINTF("suspend\n");
		theApp.m_pTaskMgr->SuspendTask(_ttoi(p->tchOpaque[0]));
	}
	else if (0 == _tcsncmp(_T("create"), p->tchDiagCmd, _tcsclen(_T("create")))) {
		DPRINTF("create\n");
		theApp.m_pTaskMgr->CreateTask(p->tchOpaque[0]);
	}
	else if (0 == _tcsncmp(_T("destroy"), p->tchDiagCmd, _tcsclen(_T("destroy")))) {
		DPRINTF("destroy\n");
		theApp.m_pTaskMgr->DestroyTask(_ttoi(p->tchOpaque[0]));
	}
	else {
		DPRINTF("nop\n");
	}

	return;
}

void CDiagnostics::vExec_LocalDiagCommandLine(WPARAM wParam, LPARAM lParam)
{
	PTST_DIAG_REQUEST_ITEM p = (PTST_DIAG_REQUEST_ITEM)wParam;

	if (nullptr == p)
		return;

	/* Invoke Diag Request */
	switch (p->enDaigCmdCat) {
	case tenDiagCmdCat::UNKNOWN:
		break;
	case tenDiagCmdCat::TASK:
		vExec_LocalDiagTask(p);
		break;
	case tenDiagCmdCat::DIAG:
		break;
	case tenDiagCmdCat::TR:
		vExec_LocalDiagTrCode(p);
		break;
	default:
		;
	}

	if (nullptr != p) {
		delete p;
		p = nullptr;
	}

	return;
}

 

도움이 되셨기를 바라며 이상 마칩니다. 

반응형