Rozdział XVI - Obiekt Zdarzeń


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}.

 

Teoria

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

Przykład

 

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
    }
}

Analiza

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.

Dodatek w Pascalu

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.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.