Technical Notes

Win32 Event Objects

Previous | Home

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.

LPSECURITY_ATTRIBUTES lpEventAttributes
This is a pointer to a 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.
BOOL bManualReset
This parameter is used to indicate whether the event object's state will be automatically reset or will be manually done. If you specify true here, then once the event object's state shifts to signalled it will not revert back to the unsignalled state until you call ResetEvent. In the example above we let the system reset the event object's state back to unsignalled automatically.
BOOL bInitialState
You specify the state in which the event object starts off with here. If this is set to true, then the object is created in the signalled state and vice versa. Further, if you specify true here, then any subsequent calls to WaitForSingleObject will cause it to return immediately. In the example, as is evident, the object starts of in the unsignalled state.
LPCTSTR lpName
Every event object can also be identified by a name which is specified here as a pointer to a null-terminated string. If a named event object with the same name already exists then 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,

In our example therefore, the function will return even if the event has not been signalled in the case that the specified number of milliseconds expire first. You can find this out by inspecting the return value which will be equal to 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;
	}
}

Previous | Home 1