2010. 7. 20. 13:06

Message Pumping Sleep 코드

보통, MFC에서든 아니든, 윈도우 메시지 핸들러 Context에서 Sleep(...)을 하면 화면이 먹통이 됩니다.
void CMainFrame::OnPaint()
{
...
	RunCalc(); // 대단히 CPU 소모적인 작업으로 이뤄진 함수
...
}

VOID CMainFrame::RunCalc()
{
...
	for (;;) // 대단히 소모적인 작업이 진행되는 Loop
	{
...
		// OnPaint()등 메시지 핸들러에서 CPU 소비가 높은 작업을 하니 화면이 먹통이 되었다.
		// 그래서, 쉬엄쉬엄 해라고 Sleep(...)을 넣어줬지만,...
		::Sleep(1000); // <-- 이걸 넣어도 먹통이다~
...
	}
...
}

위 예는 OnPaint() Context에서 호출이며,
다른 OnXXXX 함수에서도 마찬가지 입니다.

OnPaint()와 같은 메시지 핸들러 Context의 Thread는 UI Display가 이뤄져야 하는데,
이것이 ::Sleep(...) 함수에 의해 일정시간동안 Suspend하니 화면이 먹통이 되죠.

그래서, 보통이면, UI Thread와 Work Thread를 분리하여 작업하기도 하지만,
이것또한 귀찮을때가 많습니다.

::Sleep(...) 함수가 Thread를 Suspend하는 것이라,
UI를 그려야 하는 Thread와 작업을 해야 하는 Thread가 같은 경우에,
이러한 현상이 생긴다고 할 수 있습니다.

따라서, ::Sleep(...)대신에 아래와 같이,
메시지 펌핑을 하는 CAppUtil::SleepMsg(...)을 사용하면, 위 문제를 해결할 수 있습니다.

class CAppUtil
{
...
public:
	static VOID SleepMsg(IN DWORD dwMilliseconds);
...

VOID CAppUtil::SleepMsg(IN DWORD dwMilliseconds)
{
	DWORD dwStart	= 0;
	MSG	  msg		= {0,};

	dwStart  = GetTickCount();
	while (GetTickCount() - dwStart < dwMilliseconds)
	{
		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		// Lower CPU Usage
		::Sleep(10);
	}
}

::Sleep(10)의 10은 적당한 값이면 됩니다.
위 SleepMsg(...)를 넣으면, Sleep(...)과 동일하게 작동하지만, Message Pumping을 수행합니다.
즉, WM_PAINT 처리가 되므로, UI가 먹통이 되지 않습니다. !!!

단, 주의해야 하는것은,
메시지 핸들러 내부에서 되도록이면, CPU 작업이 큰 것을 넣지 않도록 하는것이 중요합니다.
위 함수는, 메시지 펌핑하면서, Sleep과 동일한 결과를 얻기 위해, 시간 만료를 체크하도록 되어 있습니다.
DispatchMessage(...)를 통해 OnPaint(...)와 같은 함수가 호출되는데, 만일
SleepMsg(10초) 했는데, OnPaint(...)에서 1분동안 응답을 안해주면, SleepMsg(10초)는 실제 1분동안
리턴이 되지 않을 것입니다.

따라서, 되도록이면, OnXXXX함수에서는 시간이 오래걸리는 작업을 하지 않는것이 좋습니다.~

(주의)
SleepMsg(...) 내부에서, WM_QUIT / WM_CLOSE등일때 Loop를 벗어난다던지,
그때 리턴값을 FALSE로 하여 계속된 Loop를 벗어날 수 있게 한다던지...
등등, UI Thread가 종료될때의 처리도 해주면, 좀더 버그를 줄일 수 있을 겁니다.