Event objects are used in Win32 primarily for thread synchronization, i.e., for smoothly co-ordinating the running of multiple threads. It is often used by asynchronous functions to notify its completion. For eg., when you call the Win32 ReadFile
function it is possible that the read operation might take an indefinite amount of time to complete; in such cases it would be convenient if we could simply call the function and have it notify us when it is done while we are busy doing something else. It is for situations like these that event objects exist.
Event objects are represented in Win32 by a handle. For eg., you would create an event handle like this,
HANDLE hEvent;
But the handle is not valid (obviously) until we call CreateEvent
. An event can be in one of the 2 possible states - signalled and unsignalled. When the state of the event shifts from unsignalled to signalled all threads waiting upon the event are released to continue execution. Threads can wait upon an event by calling WaitForSingleObject
or WaitForMultipleObjects
. When either of these functions are called, execution stops until the event (the handle to which is supplied as a parameter) is set or signalled. The actual shifting of the object's state occurs when a call to SetEvent
is made. All this will be immediately clear to you once you see an example. First let us see how event objects are created.
Creating an Event object
As mentioned before you create an event object by calling CreateEvent
in the following manner,
HANDLE hEvent; hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );The
CreateEvent
function has been declared in winbase.h like this,
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName );
Let us see what each of these parameters mean.
SECURITY_ATTRIBUTES
structure which has a boolean member variable called bInheritHandle
which can be set to true in order to specify that the handle returned by this function can be inherited by child processes. You can also set lpEventAttributes
to NULL
to indicate that the returned handle is not to be inherited by child processes.
ResetEvent
. In the example above we let the system reset the event object's state back to unsignalled automatically.
WaitForSingleObject
will cause it to return immediately. In the example, as is evident, the object starts of in the unsignalled state.
CreateEvent
simply returns the handle to that object. If lpName
is NULL
then a new event object is created without a name.
If the call to CreateEvent
fails for some mysterious reason then it simply returns NULL
which you can check for.
Just having an event object created is no good unless you can put it to some use. Exactly how we do that we will look at next.
Once you have an event object, you can use it to make threads wait upon the event. For eg.,
DWORD dwStatus; dwStatus = WaitForSingleObject( hEvent, 100 );
The WaitForSingleObject
function has been declared like this,
DWORD WaitForSingleObject( HANDLE hHandle, //handle to any object that can be waited upon DWORD dwMilliseconds //timeout value );
The first parameter, as must be evident is the handle to the event object we created earlier. But it is interesting to note that this can also be a handle to a thread or a process or any of the other handles specified at the MSDN site. For instance, if you have another thread running and wish to be notified when it is done processing, you can simply call WaitForSingleObject
passing the handle to that thread as its first parameter. For eg., this call
WaitForSingleObject( hThread, INFINITE );would cause execution to be suspended until the other thread terminates.
The second parameter indicates the timeout value in milliseconds, i.e., the function waits until one of the following 2 things happen,
WAIT_TIMEOUT
in case of a timeout and WAIT_OBJECT_0
if the event was signalled. You can also specify the constant INFINITE
for the second parameter in order to specify that execution should proceed only after the event is signalled or in other words - wait infinitely.
You will gain a more wholesome understanding of the concepts discussed so far when you look at a complete example. What follows is a little console application that uses events to signal the completion of execution of a thread that is spawned from main
.
A Complete Example Illustrating Event Objects
#include <windows.h> #include <stdio.h> #include <conio.h> DWORD ThreadFunction( LPVOID lpArg ); void main() { HANDLE hEvent; HANDLE hThread; DWORD dwThreadID; hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if( hEvent == NULL ) //oops! { printf( "CreateEvent() failed!\n" ); return; } hThread = CreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadFunction, (LPVOID)hEvent, NULL, &dwThreadID ); if( hThread == NULL ) //oops! { printf( "CreateThread() failed!\n" ); CloseHandle( hEvent ); return; } printf( "\nmain() - Waiting..." ); WaitForSingleObject( hEvent, INFINITE ); printf( "\nmain() - wait over.\n" ); //clean-up CloseHandle( hEvent ); CloseHandle( hThread ); printf( "\nPress any key.\n" ); getch(); } DWORD ThreadFunction( LPVOID lpArg ) { HANDLE hEvent = (HANDLE)lpArg; printf( "\nThreadFunction() - Sleeping for 4 seconds..." ); Sleep( 4000 ); //sleep for 4 seconds printf( "done. Setting event.\n" ); SetEvent( hEvent ); //set the event state to signalled return 0L; }
The Output
main() - Waiting... ThreadFunction() - Sleeping for 4 seconds...done. Setting event. main() - wait over. Press any key.
We start off by creating an event object that is automatically reset. We then spawn a thread from main
passing the handle to the event object as its argument. After this, main
simply waits for the signal from the thread function by calling WaitForSingleObject
.
The thread on the other hand simply sleeps for 4 seconds before changing the event object's state from unsignalled to signalled by calling the SetEvent
function which takes the handle to the event object as its parameter. The moment the event object's state is changed to signalled, the WaitForSingleObject
call returns in main
causing the statements following the call to be executed.
It is to be noted that in main
, instead of idly waiting for the event to occur we could do something useful by slightly changing the code to something like this,
... DWORD dwStatus; while( true ) { dwStatus = WaitForSingleObject( hEvent, 100 ); //wait for 100 msecs //or for event to occur if( dwStatus == WAIT_TIMEOUT ) { //do something useful here while the thread is still //doing its thing } else if( dwStatus == WAIT_OBJECT_0 ) { //the event has occured, you might want to //do something in response here //break out of the loop once the event has happened break; } }