Windows高精度微秒級(併發)定時器實現
阿新 • • 發佈:2019-02-08
自從上次封裝微秒延時函式後,利用空閒時間試著封裝一個微秒定時器(類似MFC定時器形式)使用起來效果還不錯。
關於定時器的幾點介紹:
1.設計採用了自動釋放定時器節點方式(增加虛解構函式在內部做相關釋放判斷,即使用完不釋放節點也沒關係);
2.設計採用了雙向連結串列方式做定時器節點(為了方便起見,沒有采用環形雙向連結串列);
3.增加了第三引數為回撥函式(採用MFC風格,如果第三個引數不為空,超時後呼叫回撥函式,否則呼叫預設函式);
4.設計採用了並行方式(可以同時啟動多個ID不相同的定時器,並且每個定時器之間沒有影響);
5.釋放採用了先退出定時器執行緒,後刪除節點風格(防止資料異常造成崩潰)。
函式定義如下:
//引數一 定時器ID號,引數二 定時時間(微秒),引數三 回撥函式(不需要設定NULL或者不寫)
int TimeRun(int id,int outtime,void (CALLBACK* lpfnTimer)(/*HWND, UINT, UINT_PTR, DWORD*/) = NULL);//定時器啟動函式
//引數一 定時器ID號
void TestKillTimer(int id);//定時器銷燬函式 (可以不使用)
void CALLBACK Test123();//測試寫的回撥函式 函式名稱及引數可以自己定 與定時器內以及定時器結構體的回撥函式配套
//第一個引數 定時器ID號(ID號用於多個定時器之間的判斷)
void TestTime(int id);//測試用的普通定時器呼叫函式 想啟動的函式寫在這個函式內
DWORD WINAPI TimerRunThread(LPVOID _this);//定時器執行緒函式
typedef struct timest//定時器資料儲存結構體
{
int id;//ID號
int outtime;//超時時間
volatile int beginRun;//執行狀態
void (CALLBACK* lpfnTimer)(/*HWND, UINT, UINT_PTR, DWORD*/);//回撥函式
timest *prev,*next;//雙向連結串列指標
}*ptimest;
typedef struct Testtimest//定時器執行結構體
{
ptimest ptime,ptimehead;//定時器執行節點以及頭節點
Testtimest()
{
ptime = 0;
ptimehead = 0;
}
virtual ~Testtimest()//解構函式內部封裝了定時器物件退出後的檢查及釋放
{
while (0 != ptimehead)
{
ptimest deletime;
deletime = ptimehead->next;
if (1 == ptimehead->beginRun)//如果定時執行緒還在執行 ,先退出執行緒再刪除
{
ptimehead->beginRun = 0;
UsSleep(ptimehead->outtime * 2);//刪除執行緒節點前要留出當前定時器的2倍睡眠時間
}
delete ptimehead;
ptimehead = deletime;
}
}
};
Testtimest testtime;
程式碼實現如下:
int TimeRun(int id,int outtime,void (CALLBACK* lpfnTimer)(/*HWND, UINT, UINT_PTR, DWORD*/))//定時器啟動實現 採用了可以多次重複建立並且刪除的安全方式
{
if (0 > id || 0 >= outtime)
{
return -1;
}
testtime.ptime = testtime.ptimehead;//為方便多次刪除後繼續用,每次先獲取首節點
if (0 != testtime.ptime)//如果頭節點不為空,判斷當前新增定時器ID是否與之前增加ID重複
{
while (id != testtime.ptime->id && 0 != testtime.ptime->next)
testtime.ptime = testtime.ptime->next;
if (id == testtime.ptime->id)//如果啟動的定時器Id與之前的重複 則不啟動
{
return -1;
}
}
if (0 == testtime.ptime)
{
testtime.ptime = new timest;
testtime.ptime->next = 0;
testtime.ptime->prev = 0;
testtime.ptimehead = testtime.ptime;
}
else
{
while (0 != testtime.ptime->next)
testtime.ptime = testtime.ptime->next;
testtime.ptime->next = new timest;
testtime.ptime->next->next = 0;
testtime.ptime->next->prev = testtime.ptime;
}
while (0 != testtime.ptime->next)
testtime.ptime = testtime.ptime->next;
testtime.ptime->id = id;//id
testtime.ptime->beginRun = 1;//啟動狀態
testtime.ptime->outtime = outtime;//超時時間
testtime.ptime->lpfnTimer = lpfnTimer;//回撥函式
HANDLE handle = CreateThread(NULL,0,TimerRunThread,testtime.ptime,0,NULL);
CloseHandle(handle);//為了C程式也能執行採用了API執行緒,並且控制代碼沒實際用處就直接釋放掉
return 0;
}
void CALLBACK Test123()//測試回撥函式實現
{
int a;
a = 2;
}
DWORD WINAPI TimerRunThread(LPVOID _this)//定時器執行緒函式實現
{
ptimest Runject = (/*decltype(_this)*/ptimest)_this;//本來想用獲取型別方式來直接獲取傳入的型別,後來想到了線上程函式中型別是void*所以只能手動寫入傳入的引數型別
while (1 == Runject->beginRun)//定時器狀態為1執行,0時退出執行緒函式,刪除定時器節點
{
UsSleep(Runject->outtime);
if (0 != Runject->lpfnTimer)//如果回撥函式不為空呼叫回撥函式
{
Runject->lpfnTimer();
}
else
{
TestTime(Runject->id);//根據ID號進行呼叫普通定時器啟動函式
}
}
return 0;
}
void TestTime(int id)//測試 定時器呼叫函式實現
{
int a = 0;
if (1 == id)
{
a = 1;
}
if (2 == id)
{
a = 2;
}
if (3 == id)
{
a = 3;
}
}
void TestKillTimer(int id)//定時器銷燬函式實現
{
ptimest deletime = testtime.ptimehead;
ptimest next,prev;
if (id == deletime->id)//首節點
{
next = testtime.ptimehead->next;
deletime->beginRun = 0;
UsSleep(deletime->outtime * 2);//為了可以退出定時器執行緒並且安全刪除定時器節點採用了2倍超時時間
delete deletime;
deletime = 0;
testtime.ptimehead = next;
if (0 != next)
{
next->prev = 0;
}
return ;
}
while (id != deletime->id && 0 != deletime)
deletime = deletime->next;
if (0 == deletime)//沒有找到Id
{
return ;
}
prev = deletime->prev;
next = deletime->next;
deletime->beginRun = 0;
UsSleep(deletime->outtime * 2);
delete deletime;
deletime = 0;
prev->next = next;
if (0 != next)
{
next->prev = prev;
}
}
int UsSleep(int us)//微秒延時函式實現
{
LARGE_INTEGER fre;
if (QueryPerformanceFrequency(&fre))
{
LARGE_INTEGER run,priv,curr,res;
run.QuadPart = fre.QuadPart * us / 1000000;
QueryPerformanceCounter(&priv);
do
{
QueryPerformanceCounter(&curr);
} while (curr.QuadPart - priv.QuadPart < run.QuadPart);
curr.QuadPart -= priv.QuadPart;
int nres = (curr.QuadPart * 1000000 / fre.QuadPart);
return nres;
}
return -1;//
}
使用方式:
TimeRun(1,500,NULL);
TimeRun(2,1000,Test123);
TestKillTimer(1);
//TestKillTimer(2);