![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr
Jerzy Wałaszek
|
Nauczymy się czym jest obiekt zdarzeń (event object) oraz jak go używać w programie wielowątkowym.

Pobierz plik z przykładem {z tego archiwum}.
Na poprzedniej lekcji zademonstrowałem sposób komunikacji pomiędzy wątkami przy wykorzystaniu prywatnych wiadomości dla okna. Pominąłem dwie inne metody: zmienne globalne oraz obiekt zdarzeń, które użyjemy na tej lekcji.
Obiekt zdarzeń jest jak przełącznik: ma tylko dwa stany: włączony i wyłączony. Gdy obiekt zdarzeń jest włączony, znajduje się w stanie "zasygnalizowanym". Gdy jest wyłączony, znajduje się w stanie "nie zasygnalizowanym". Tworzysz obiekt zdarzeń i umieszczasz w istotnych wątkach fragment kodu do obserwowania stanu tego obiektu. Gdy obiekt będzie się znajdował w stanie nie zasygnalizowanym, wątki oczekujące na niego będą uśpione. Gdy wątki są w stanie oczekiwania, zużywają niewiele czasu procesora.
Obiekt zdarzeń tworzysz wywołując funkcję
CreateEvent
o następującej składni:
CreateEvent PROTO lpEventAttributes: DWORD,\
bManualReset: DWORD,\
bInitialState: DWORD,\
lpName: DWORD
Jeśli wywołanie się powiedzie, zwróci uchwyt do nowo utworzonego obiektu zdarzeń, w przeciwnym razie zwróci wartość NULL.
Możesz zmieniać stan obiektu zdarzeń za pomocą dwóch wywołań funkcji API: SetEvent oraz ResetEvent. Funkcja SetEvent ustawia obiekt zdarzeń w stan zasygnalizowany. ResetEvent dokonuje operacji odwrotnej.
Gdy obiekt zdarzeń zostanie utworzony, w wątku śledzącym stan
tego obiektu musisz umieścić wywołanie WaitForSingleObject
o następującej składni:
WaitForSingleObject PROTO hObject:DWORD, dwTimeout:DWORD

Poniższy przykład wyświetla okno oczekujące, aż użytkownik wybierze polecenie z menu. Jeśli wybierze on opcję Uruchom Wątek, to wątek rozpocznie dzikie obliczenia. Gdy je zakończy pojawi się okienko wiadomości informujące użytkownika o zakończeniu pracy. W czasie pracy tego wątku użytkownik może wybrać opcję Zatrzymaj Wątek.
Plik THREAD2.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD
INCLUDE \masm32\INCLUDE\windows.inc
INCLUDE \masm32\INCLUDE\user32.inc
INCLUDE \masm32\INCLUDE\kernel32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib
.CONST
IDM_START_THREAD EQU 1
IDM_STOP_THREAD EQU 2
IDM_EXIT EQU 3
WM_FINISH EQU WM_USER+100h
.DATA
ClassName DB "Win32ASMEventClass", 0
AppName DB "Przykład Zdarzenia", 0
MenuName DB "FirstMenu", 0
SuccessString DB "Obliczenia zakończone!", 0
StopString DB "Wątek zatrzymany", 0
EventStop BOOL FALSE
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?
.CODE
start:
INVOKE GetModuleHandle, NULL
mov hInstance, eax
INVOKE GetCommandLine
mov CommandLine, eax
INVOKE WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
INVOKE ExitProcess, eax
WinMain PROC hInst: HINSTANCE,\
hPrevInst: HINSTANCE,\
CmdLine: LPSTR,\
CmdShow: DWORD
LOCAL wc : WNDCLASSEX
LOCAL msg : MSG
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW OR CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszMenuName, OFFSET MenuName
mov wc.lpszClassName, OFFSET ClassName
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
INVOKE RegisterClassEx, ADDR wc
INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\
ADDR ClassName, ADDR AppName,\
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,\
CW_USEDEFAULT, 300, 200, NULL, NULL,\
hInst, NULL
mov hwnd, eax
INVOKE ShowWindow, hwnd, SW_SHOWNORMAL
INVOKE UpdateWindow, hwnd
INVOKE GetMenu, hwnd
mov hMenu, eax
.WHILE TRUE
INVOKE GetMessage, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax)
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDW
mov eax, msg.wParam
ret
WinMain ENDP
WndProc PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
INVOKE CreateEvent, NULL, FALSE, FALSE, NULL
mov hEventStart, eax
mov eax, OFFSET ThreadProc
INVOKE CreateThread, NULL, NULL, eax,\
NULL, NORMAL_PRIORITY_CLASS,\
ADDR ThreadID
INVOKE CloseHandle, eax
.ELSEIF uMsg==WM_DESTROY
INVOKE PostQuitMessage, NULL
.ELSEIF uMsg==WM_COMMAND
mov eax, wParam
.IF lParam==0
.IF ax==IDM_START_THREAD
INVOKE SetEvent, hEventStart
INVOKE EnableMenuItem, hMenu, IDM_START_THREAD, MF_GRAYED
INVOKE EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_ENABLED
.ELSEIF ax==IDM_STOP_THREAD
mov EventStop, TRUE
INVOKE EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
INVOKE EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
.ELSE
INVOKE DestroyWindow, hWnd
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_FINISH
INVOKE MessageBox, NULL, ADDR SuccessString, ADDR AppName, MB_OK
.ELSE
INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc ENDP
ThreadProc PROC USES ecx Param:DWORD
ThreadLoop:
INVOKE WaitForSingleObject, hEventStart, INFINITE
mov ecx, 600000000
.WHILE ecx!=0
.IF EventStop==FALSE
add eax, eax
dec ecx
.ELSE
INVOKE MessageBox, hwnd, ADDR StopString, ADDR AppName, MB_OK
mov EventStop, FALSE
jmp ThreadLoop
.ENDIF
.ENDW
INVOKE PostMessage, hwnd, WM_FINISH, NULL, NULL
INVOKE EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
INVOKE EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
jmp ThreadLoop
ThreadProc ENDP
END start
Plik THREAD2.RC
// Stałe dla menu
#define IDM_START_THREAD 1
#define IDM_STOP_THREAD 2
#define IDM_EXIT 3
FirstMenu MENU
{
POPUP "&Wątek"
{
MENUITEM "&Uruchom Wątek", IDM_START_THREAD
MENUITEM "&Zatrzymaj Wątek", IDM_STOP_THREAD, GRAYED
MENUITEM SEPARATOR
MENUITEM "&Koniec", IDM_EXIT
}
}
W tym przykładzie demonstruję inną technikę
wątków.
.IF uMsg==WM_CREATE
INVOKE CreateEvent, NULL, FALSE, FALSE, NULL
mov hEventStart, eax
mov eax, OFFSET ThreadProc
INVOKE CreateThread, NULL, NULL, eax,\
NULL, NORMAL_PRIORITY_CLASS,\
ADDR ThreadID
INVOKE CloseHandle, eax
Jak widać tworzę obiekt zdarzeń oraz wątek podczas obsługi wiadomości
WM_CREATE. Obiekt zdarzeń tworzę w stanie nie
zasygnalizowanym z automatycznym resetowaniem. Po utworzeniu obiektu zdarzeń
tworzę wątek. Jednakże wątek nie zaczyna natychmiast pracować, ponieważ oczekuje
on, aż obiekt zdarzeń znajdzie się w stanie zasygnalizowanym, co wykonuje
poniższy kod:
ThreadProc PROC USES ecx Param:DWORD
ThreadLoop:
INVOKE WaitForSingleObject, hEventStart, INFINITE
mov ecx, 600000000
W pierwszym wierszu procedury wątku występuje wywołanie funkcji
WaitForSingleObject. Przed powrotem do procedury
wątku funkcja ta oczekuje w nieskończoność na stan zasygnalizowany obiektu
zdarzeń. Oznacza to, iż po utworzeniu wątku będzie on automatycznie w stanie
uśpienia.
Gdy użytkownik wybierze polecenie
Uruchom Wątek
z menu, ustawiamy obiekt zdarzeń w stan zasygnalizowany, jak przedstawiono
poniżej:
.IF ax==IDM_START_THREAD INVOKE SetEvent, hEventStart
Wywołanie funkcji SetEvent zmienia stan obiektu zdarzeń
na zasygnalizowany, co z kolei powoduje powrót do procedury wątku z funkcji
WaitForSingleObject i w efekcie wątek rozpoczyna swoją pracę. Gdy
użytkownik wybierze polecenie
Zatrzymaj Wątek, ustawiamy wartość zmiennej
globalnej EventStop na TRUE.
ThreadLoop:
INVOKE WaitForSingleObject, hEventStart, INFINITE
mov ecx, 600000000
.WHILE ecx!=0
.IF EventStop==FALSE
add eax, eax
dec ecx
.ELSE
INVOKE MessageBox, hwnd, ADDR StopString, ADDR AppName, MB_OK
mov EventStop, FALSE
jmp ThreadLoop
.ENDIF
.ENDW
INVOKE PostMessage, hwnd, WM_FINISH, NULL, NULL
INVOKE EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
INVOKE EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
jmp ThreadLoop
Powoduje to wstrzymanie wykonywania obliczeń w wątku i skok do wywołania
WaitForSingleObject. Zwróć uwagę, iż nie musimy ręcznie resetować obiektu
zdarzeń do stanu nie zasygnalizowanego, ponieważ parametr
bManualReset
wywołania funkcji CreateEvent ustawiamy na
FALSE.
Ta sama aplikacja w Pascalu: (program korzysta z plików zasobów, który należy umieścić w katalogu projektowym pod nazwą RSRC.RC).
{********************************
** I Liceum Ogólnokształcące **
** w Tarnowie **
** mgr Jerzy Wałaszek **
********************************}
program Thread2;
uses Windows;
const
IDM_START_THREAD = 1;
IDM_STOP_THREAD = 2;
IDM_EXIT = 3;
WM_FINISH = WM_USER + $100;
ClassName = 'Win32ASMEventClass';
AppName = 'Przykład Zdarzenia';
MenuName = 'FirstMenu';
SuccessString = 'Obliczenia zakończone!';
StopString = 'Wątek zatrzymany';
var
EventStop : boolean;
hInstance : HINST;
hwnd : HANDLE;
hMenu : HANDLE;
ThreadID : longword;
ExitCode : longword;
hEventStart : HANDLE;
procedure ThreadProc(Param:DWORD);
var
i : longword;
begin
while true do
begin
WaitForSingleObject(hEventStart,INFINITE);
i := 600000000;
while i > 0 do
if not EventStop then
i := i - 1
else
begin
MessageBox(hwnd,StopString,AppName,MB_OK);
EventStop := false;
break;
end;
if not EventStop then
begin
PostMessage(hwnd,WM_FINISH,0,0);
EnableMenuItem(hMenu,IDM_START_THREAD,MF_ENABLED);
EnableMenuItem(hMenu,IDM_STOP_THREAD,MF_GRAYED);
end;
end;
end;
function WndProc(hWnd:HANDLE;uMsg:UINT;wParam:WPARAM;lParam:LPARAM)
: longint;
begin
case uMsg of
WM_CREATE:
begin
hEventStart := CreateEvent(0,false,false,0);
CloseHandle(CreateThread(0,0,@ThreadProc,0,
NORMAL_PRIORITY_CLASS,ThreadID));
end;
WM_DESTROY: PostQuitMessage(0);
WM_COMMAND:
if lParam = 0 then
case (wParam and $ffff) of
IDM_START_THREAD:
begin
SetEvent(hEventStart);
EnableMenuItem(hMenu,IDM_START_THREAD,MF_GRAYED);
EnableMenuItem(hMenu,IDM_STOP_THREAD,MF_ENABLED);
end;
IDM_STOP_THREAD:
begin
EventStop := true;
EnableMenuItem(hMenu,IDM_START_THREAD,MF_ENABLED);
EnableMenuItem(hMenu,IDM_STOP_THREAD,MF_GRAYED);
end;
else DestroyWindow(hWnd);
end;
WM_FINISH: MessageBox(0,SuccessString,AppName,MB_OK);
else Result := DefWindowProc(hWnd,uMsg,wParam,lParam);
end;
end;
function WinMain(hInst,hPrevInst:HINST;CmdLine:LPSTR;CmdShow:DWORD)
: longint;
var
wc : WNDCLASSEX;
msg: MSG;
begin
with wc do
begin
cbSize := sizeof(WNDCLASSEX);
style := CS_HREDRAW or CS_VREDRAW;
lpfnWndProc := @WndProc;
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := hInst;
hbrBackground := COLOR_WINDOW + 1;
lpszMenuName := MenuName;
lpszClassName := ClassName;
hIcon := LoadIcon(0,IDI_APPLICATION);
hIconSm := hIcon;
hCursor := LoadCursor(0,IDC_ARROW);
end;
RegisterClassEx(wc);
hwnd := CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
300,200,0,0,hInst,0);
ShowWindow(hwnd,SW_SHOWNORMAL);
UpdateWindow(hwnd);
hMenu := GetMenu(hwnd);
while GetMessage(msg,0,0,0) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
Result := msg.wParam;
end;
begin
EventStop := false;
hInstance := GetModuleHandle(0);
ExitProcess(WinMain(hInstance,0,GetCommandLine,SW_SHOWDEFAULT));
end.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe