XSleep
從範例XSleep - An alternative to the Sleep() function學習Thread,作者本身並沒有很多的實用
經驗,但希望能讓更多的人來接觸執行緒這個觀念。
在單一執行緒的狀況下,在執行一個程式或動作時,就無法同時執行另一個動作。
比如當你在列印文章時,是無法做其它的動作。當你在做一件事情時,就無法同時做另一件事情。
有單一執行緒(single thread),當然會成就一個以上的執行緒(多執行緒,multiple threads)。
在MS-DOS中,並不是一個多執行緒的系統,處理任何事情時,其它的動作都必須停止動作;
到了MS-Window的出現才慢慢有了執行緒的觀念,想知道有關更多執行緒或多工的細節及動作
可以參考
【Win 32 多緒程式設計 (Multithreading Applications in Win32) 
   The Complete Guide to Threads(執行緒 完全手冊) 】
這本書介紹的很詳細,但有些內容是需要系統的概念才會比較能夠瞭解意思,可以參考看看。
先來翻譯一下XSleep的文章介紹:

這篇文章是由Anui Seth所發表出來的
在Win 9x/NT系統,編譯器為VC6.0

如果使用在程式中使用Sleep()函式時,會很快的瞭解到你的應用程式出現封鎖(block)的狀態,
也就是無法做其它的事情。這是因為Sleep()函式沒有處理訊息泵(message pump。大陸用語:消息泵。
就是說讓這個訊息能流動著),所以導致應用軟體在某段時間內出現懸掛(hanging,大陸用語稱:死機)的狀態,
所以Anui Seth撰寫另一個替代的函式,即使Sleep出現在程式之中也可以處理訊息泵,使全部的訊息都能流動著。
你可以發現並不是在所有的時候都需要使用到XSleep()。舉個例子,在以console為主的應用軟體。
在一些場合時你對XSleep()的需要可能會超過Sleep(),特別是,如果在GUI應用軟體中使用Sleep()更加明顯。
XSleep()程式碼中只使用到Win32函式呼叫,所以可以在MFCWin32的應用軟體上使用。

在使用XSleep時需把XSleep.h標題檔引入你的專案中,而這個XSleep()呼叫時需要配合一個以毫秒為單位的參數。
如:

XSleep(1000);  //sleep for 1 second

提供demoVC6.0專案讓大家下載,這個demo產生兩個執行緒(thread),而且都sleep 2秒中的時間。

程式:
檔案:XSleep.h
#ifndef _XSLEEP_H_
#define _XSLEEP_H_

void XSleep(int nWaitInMSecs);

#endif // _XSLEEP_H_

檔案:XSleep.cpp
#include <windows.h>

// This structure is used internally by the XSleep function 
struct XSleep_Structure
{
    int duration;
    HANDLE eventHandle;
};


//////////////////////////////////////////////////////////////////////
// Function : XSleepThread()
// Purpose : The thread which will sleep for the given duration
// Returns : DWORD WINAPI
// Parameters: 
// 1. pWaitTime -
//////////////////////////////////////////////////////////////////////

DWORD WINAPI XSleepThread(LPVOID pWaitTime)
{
    XSleep_Structure *sleep = (XSleep_Structure *)pWaitTime;

    Sleep(sleep->duration);
    SetEvent(sleep->eventHandle);

    return 0;
}

//////////////////////////////////////////////////////////////////////
// Function : XSleep()
// Purpose : To make the application sleep for the specified time
// duration.
// Duration the entire time duration XSleep sleeps, it
// keeps processing the message pump, to ensure that all
// messages are posted and that the calling thread does
// not appear to block all threads!
// Returns : none
// Parameters: 
// 1. nWaitInMSecs - Duration to sleep specified in miliseconds.
//////////////////////////////////////////////////////////////////////

void XSleep(int nWaitInMSecs)
{
    XSleep_Structure sleep;

    sleep.duration = nWaitInMSecs;
    sleep.eventHandle = CreateEvent(NULL, TRUE, FALSE, NULL);

    DWORD threadId;
    CreateThread(NULL, 0, &XSleepThread, &sleep, 0, &threadId);

    MSG msg;

    while(::WaitForSingleObject(sleep.eventHandle, 0) == WAIT_TIMEOUT)
    {
        //get and dispatch messages
        if(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
    }
    CloseHandle(sleep.eventHandle);
}


接著來介紹XSleep中所使用的函式與資料型態:
一.資料型態:
(1)DWORD 
==>double word的意思

(2)MSG(針對訊息所產生資料的結構)
typedef struct tagMSG
{
	HWND		hwnd;
	UINT		message;
	WPARAM	wparam;
	LPARAM		lparam;
	DWORD		time;
	POINT		pt;
}MSG,*PMSG;
(3)HANDLE+
==>其實是個指標,指向作業系統記憶體空間中的某樣東西,那東西不允許你直接取得。
   你的程式不能夠直接取用它,為的是維護系統的完整性與安全性。


如果不清楚結構中的型態與意思,可以參考《Programmong Windows 5/e》或查詢MSDN

.函式:
•Sleep:是用來延遲目前正在程式的程式,型式如下:
	VOID Sleep( DWORD dwMilliseconds );

參數以毫秒為單位,所以欲延遲1秒時==>Sleep(1000);

CreateEvent:產生或開啟一個具名或不具名的事件性的物件
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
);

XSleep所使用的是:CreateEvent( NULL , TRUE , FALSE , NULL );
第一NULL:代表這個事件性的物件不能夠繼承 ==>安全性的參數設定
第二TRUECreateEvent產生的是一個手動重置的事件性的物件,需要使用ResetEvent函式來讓這個物件的狀態
設為nonsignal==>重置的方式設定
第三FALSE:代表事件性物件的初始狀態(initial state)nonsignaled ==>初始狀態的設定
第四NULL:產生一個不具名的物件  ==>具不具名的設定

回傳值:回傳所產生事件性物件的handle。

補充1manual-reset(手動重置),大陸的翻譯是(人工重置),在其它領域如電機,通常稱為手動復歸(MR)
相對於manual-reset,還有一個自動重置(auto-reset)
補充2事件性的物件(Event object)是同步物件之一,用於通知執行緒的發生,有signalednonsignaled兩種狀態。
建立時可以選擇自動重置(自動從signaled狀態恢復到nonsignaled狀態),或者以手動重置的方式。

SetEvent:把所指定的事情性的物件狀態設為signaled

函式型式:	BOOL SetEvent( HANDLE hEvent );
參數hEvent:所要處理的事件性物件。

回傳值:回傳非零值時,代表成功;否則為失敗。

CreateThread:產生一個執行緒
函式原型如下:

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  SIZE_T dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  DWORD dwCreationFlags,
  LPDWORD lpThreadId
);

XSleep所使用的是:CreateThread(NULL,0,&XSleepThread,&sleep,0,&threadId);
第一個參數:是用來說明這個執行緒的security屬性,NULL表示預設狀態。
第二個參數:是指新執行緒自己的堆疊空間,0表示預設大小:1MB
第三個參數:是指新執行緒開始的起始位址。是一個函式指標。而在C語言中函式名稱即代表函式指標)
第四個參數:此值將被傳送到所指定的新執行緒函式去,作為新執行緒函式的參數
第五個參數:允許你產生一個暫時懸掛的執行緒。預設情況是「立即開始執行」
第六個參數:新執行緒ID會被傳回到這裡
補充3產生執行緒時,會將第四個參數(&sleep)傳送到新執行緒函式(&XSleepThread)中作為參數。

●訊息迴圈(Message loop)
while(::WaitForSingleObject(sleep.eventHandle, 0) == WAIT_TIMEOUT)
{
	//get and dispatch messages
	if(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		::TranslateMessage(&msg);
		::DispatchMessage(&msg);
	}
}
Windows會將事件(Event)轉換為一個「訊息(Message)」,並將訊息(Message)放入程式的
訊息佇列(Message queue)中。程式會通過一塊(block)所謂的的「訊息迴圈(Message loop)」
的程式碼從訊息佇列(Message queue)中取出訊息(Get Message)或查看訊息(Peek Meesage),
判斷事情的狀態。
這個程式的訊息迴圈以WaitForSingleObject呼叫開始,先判斷執行緒的狀態是否繼續執行迴圈。
接著以PeekMessage來看一下訊息佇列,若訊息佇列中存在訊息時,以TranslateMessage(&msg)msg結構傳給windows,進行一些鍵盤轉換。
接著又利用DispatchMessage(&msg)將訊息發送給視窗訊息處理程式

WaitForSingleObject

函式的型式如下:
DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD dwMilliseconds
);
第一個參數:處理的物件
第二個參數:超時時間或期限時間(Time-out interval),單位為毫秒(millisecoonds)。
            假如這個時間已消逝(elapse)或完畢,既使物件的狀態是nonsignaled,函式就會返回(return)
                 假如dwMilliseconds是零時,函式會檢查物件的狀態並且立即回值。
            假如dwMillisecondsINFINITE時,這個函式的期限時間不會消逝或結束。    

當下列事件發生時,這個函式就會回傳一些用來表示事件狀態的值:
(1)所指定的物件在signaled狀態
(2)time-out interval(超時時間或期限時間)已經消逝或結束了
而當函式成功時,所表示事件狀態的值如下列表格:

Return Code

Description

WAIT_ABANDONED

The specified object is a mutex object that was not released by the thread that owned the mutex object before the owning thread terminated. Ownership of the mutex object is granted to the calling thread, and the mutex is set to nonsignaled.

WAIT_OBJECT_0

The state of the specified object is signaled.

WAIT_TIMEOUT

The time-out interval elapsed, and the object's state is nonsignaled.

XSleep所使用的型式:WaitForSingleObject(sleep.eventHandle, 0) == WAIT_TIMEOUT
第二個參數設為0,意指函式會檢查物件的狀態並立即回值,
而WAIT_TIMEOUT指超時並且物件的狀態是nonsignaled
因為在XSleepThread中還沒有SetEvent之前,物件的狀態都是nonsignaled
直到Sleep結束,就會呼叫SetEvent,此時WaitForSingleObject的返回值就不是WAIT_TIMEOUT!
就離開訊息迴圈了!

如果我們要等待執行緒1秒鐘,可以使WaitForSingleObjectdwMilliseconds設為1000 
如果要等到執行緒結束,可以將WaitForSingleObjectdwMilliseconds設為INFINITE 
參數 2 中的 INFINITE Windows.inc 中有定義,意思是無窮等待。

補充4thread(執行緒),大陸翻譯成進程。
補充5time-out interval翻成超時時間或期限時間可能較不容易曉得是什麼,就是給予執行緒一個期限讓它執行。

PeekMessage:查看一下訊息佇列,並且可設定參數來決定是否保留訊息佇列中的訊息
•TranslateMessage:轉譯某些鍵盤訊息
•DispatchMessage:將訊息發送給視窗訊息處理程式

以上這三個函式十分常見用法固定,所以就不多加介紹了!

網路常見問題集:
Q:問題: 請問api函數peekmessagegetmessage的區別?
A:
兩個函數主要有以下兩個區別:
1.GetMessage將等到有合適的訊息時才返回,PeekMessage只是撇一下訊息佇列。
2.GetMessage會將訊息從佇列中刪除,PeekMessage可以設置最後一個參數wRemoveMsg來
    決定是否將訊息保留在佇列中。
參考文章:
線程的同步
 
回目錄
Written By James On 2004/02/08 
Hosted by www.Geocities.ws

1