12. Threads

Page 193
  12. Threads  
   
  Visual Basic itself does not support the creation of threads in a VB application (except in a very limited fashion for ActiveX DLLs and ActiveX controls), although the CreateThread API function does work in VB. Moreover, since the proper creation and use of threads requires careful circumspection, I cannot recommend creating threads in VB applications. This does not mean, however, that manipulating existing threads is not useful, as we will see.  
 
  Thread Handles and Thread IDs  
   
  In general, the story of thread handles, pseudohandles, and IDs parallels that of processes.  
   
  We have seen that the CreateProcess function returns the thread ID and a thread handle for the first (and only) thread running in the newly created process. Also, the CreateThread function returns a system-wide thread ID.  
   
  A thread can use the GetCurrentThreadId function to get its own thread ID. We have seen that the function GetWindowThreadProcessId retrieves the thread ID of the thread that created a particular window (as well as process ID of the process that owns that thread).  
   
  To quote the Win32 documentation:  
  The Win32 API does not provide a way to get the thread handle from the thread identifier. If the handles were made available this way, the owning process could fail because another process unexpectedly performed an operation on one of its threads, such as suspending it, resuming it, adjusting its priority, or terminating it. Instead, you must request the handle from the thread creator or the thread itself.  
   
  Finally, a thread can call the GetCurrentThread function to retrieve a pseudohandle to itself. As with process pseudohandles, a thread pseudohandle is valid only  
Page 194
   
  for the calling process and it cannot be inherited. Also as with processes, we can use the DuplicateHandle function to get a real handle to a thread from a pseudohandle.  
 
  Thread Priority  
   
  The term multitasking, also called multiprogramming, refers to the ability to manage multiple processes (or multiple threads) with a single processor. On the other hand, multiprocessing refers to the management of multiple processes or multiple threads with more than one processor.  
   
  It is only in a multiprocessor environment that a computer can execute instructions from two different processes at the same time. However, even with a single processor, task switching can make it appear that more than one process is executing instructions at the same time.  
   
  In the older 16-bit Windows operating system, there was only one thread in the entire system. Moreover, 16-bit Windows used a cooperative multitasking approach, meaning that each application was responsible for relinquishing the system's thread so that other applications could execute. If a program running under 16-bit Windows was performing a time-intensive task, such as formatting a floppy disk, all other loaded applications had to wait. Worse yet, if an errant program went into an infinite loop, the entire system would become unusable, requiring a cold reboot.  
   
  Thread Priority Levels  
   
  Win32 is much different. First, it is multithreaded, which allows for the possibility of multiprocessing, among other things. Second, it uses a preemptive multitasking model, where the operating system controls when and for how long each thread gets CPU time, which is allocated in time slices. A time slice is referred to by Windows as a quantum.  
   
  The length of a quantum is hardware dependent and can actually vary from thread to thread. Just to give a reference point, the base value is 20 milliseconds for Windows 95, 30 milliseconds for Windows NT Workstation (on a Pentium-based system), and 180 milliseconds for Windows NT Server.  
   
  Let us explain, in very general terms, how Windows allocates quantums to the threads in a system. The process is quite similar, but not identical, for Windows 9x and Windows NT.  
   
  Every thread in the system has a priority level, which is a number between 0 and 31. In brief, here are the facts.  
Page 195
   
  If there are any threads with priority 31 that require CPU time, that is, that are not idle, the operating system cycles among these threads (without regard to which process they are contained in), giving each of them a time slice in turn. Lower-priority threads do not receive any time slices and thus will not execute. If no priority 31 threads are active, the operating system looks for active threads with priority level 30, and so on. Keep in mind, however, that threads are quite often idle. In fact, just because an application is loaded doesn't mean that any of its threads are active. Thus, lower-priority threads do get a chance to execute. Moreover, if the user presses a key that is intended for a process whose threads are idle, the operating system will temporarily assign the CPU to the relevant thread so that it can process the keystroke.  
   
  Priority 0 is reserved exclusively for a special system thread, called the zero page thread, which clears unused portions of memory. There is also something called the idle thread, which runs at priority level 0, polling the system looking for something to do.  
   
  If a thread of a certain priority is running when the operating system determines that a thread of higher priority requires CPU time (for instance, the thread receives a message that a mouse click has taken place), the operating system will preempt the lower-priority thread immediately, giving the CPU to the higher-priority thread. Thus, a thread may not be able to complete a given time slice.  
   
  To switch from one thread to another, the system performs a context switch. This is the process of saving the state of the CPU (registers and stack) and loading the corresponding values of the target thread.  
   
  Assigning Thread Priority  
   
  Thread priority is assigned in a two-step process. First, every process is assigned a priority class when it is created. This class can be read using the GetPriorityClass function and changed using the SetPriorityClass function. Table 12-1 shows the process priority class names, levels, and the constants used with these aforementioned functions (as well as CreateProcess).  
Table 12-1. Process Priority Levels
Priority Class Name Priority Class Level Symbolic Constant
Idle 4 IDLE_PRIORITY_CLASS=&H40
Normal 8 NORMAL_PRIORITY_CLASS=&H20
High 13 HIGH_PRIORITY_CLASS=&H80
Realtime 24 REALTIME_PRIORITY_CLASS=&H100


Page 196
   
  Most processes should simply be given the normal priority class level. However, for some applications, such as system monitoring applications, it may be more appropriate to assign an idle priority. Realtime priority should generally be avoided because it will initially give the threads in the process higher priority than system threads such as the keyboard, mouse, disk cache-flushing and Ctrl-Alt-Del monitoring threads! It may be appropriate for short-term critical hardware-related processes.  
   
  Now, when a thread is first created, its default priority level is set to the priority class level of the process that created the thread. However, the SetThreadPriority function:  
 
  BOOL SetThreadPriority(
   HANDLE hThread, // handle to the thread
   int nPriority   // thread priority level
);
 
   
  can be used to alter a thread's priority. The nPriority parameter is used to alter the thread's priority relative to the priority of the thread's process. The possible values of nPriority and their effects are shown in Table 12-2.  
Table 12-2. Thread Priority Level Adjustments
Constant Thread Priority Level
THREAD_PRIORITY_NORMAL Priority class level
THREAD_PRIORITY_ABOVE_NORMAL Priority class level + 1
THREAD_PRIORITY_BELOW_NORMAL Priority class level - 1
THREAD_PRIORITY_HIGHEST Priority class level + 2
THREAD_PRIORITY_LOWEST Priority class level - 2
THREAD_PRIORITY_IDLE Set thread priority to 1 for all process priority classes except Realtime. In this case, set priority level to 16.
THREAD_PRIORITY_TIME_CRITICAL Set priority level to 15 for all process priority classes except Realtime. In this case, set priority level to 31.


   
  Thread Priority Boosting and Quantum Stretching  
   
  The priority range from 1 to 15 is known as the dynamic priority range and the range from 16 to 31 is the realtime priority range.  
   
  Under Windows NT, a thread whose priority range is in the dynamic range may have its priority temporarily boosted by the operating system at various times. Accordingly, a thread's unaltered priority level (set by the programmer through the API functions) is referred to as its base priority level. In fact, the Windows NT API function SetProcessPriorityBoost can be used to turn priority boosting on and off. (This function is not supported under Windows 9x.)  
Page 197
   
  Also, there are occasions when a thread's time slice is temporarily lengthened.  
   
  In an effort to smooth operation, Windows will boost a thread's priority or increase its time slice length under the following conditions:  
   
  When a thread is in the foreground process, that is, the process whose active window has the input focus  
   
  When a thread first enters its wait state  
   
  When a thread leaves its wait state  
   
  If a thread is not getting any (CPU time, that is)  
   
  Thread States  
   
  Threads can exist in one of several states:  
 
  Ready
In the pool of threads that are waiting to execute.
 
 
  Standby
Selected to run in the next quantum for a processor. Only one thread per processor can be in standby state.
 
 
  Running
Executing on a processor.
 
 
  Waiting (also called idle or suspended)
In the wait state. When this state ends, the thread either begins executing (running state) or enters the ready state.
 
 
  Transition
Ready to run but its stack is paged out to the hard disk page file, rather than being in physical memory. Once the stack contents are returned to memory, the thread enters the ready state.
 
 
  Terminated
Completed executing all of its instructions. Subsequently, it may or may not be deleted. If not, the system can reinitialize the thread for subsequent use.
 
 
  Thread Synchronization  
   
  When multiple threads are running, it is often important for them to cooperate in some way. For instance, if more than one thread is attempting to access some global data, each thread needs to avoid contaminating the data for the other threads. Also, there are occasions when one thread may need to be informed when another thread has finished a task. This communication may need to take place between threads in a single process or in different processes.  
Page 198
   
  Thread synchronization is a general term that refers to the process of thread cooperation and communication. Note that thread synchronization needs to involve the system itself, to act as a mediator. (A thread cannot communicate with another thread completely on its own.)  
   
  Win32 has several methods for thread synchronization. One method may be more appropriate for a given situation than another method. We will take a brief look at these methods and use some of them in some VB applications.  
   
  Critical Sections  
   
  One method of thread synchronization involves the use of critical sections. This is the only thread synchronization method that does not require the involvement of the Windows kernel. (It is not a kernel object.) However, it can be used to synchronize threads only within a single process.  
   
  The first step is to declare a global variable of type CRITICAL_SECTION, say:  
 
  CRITICAL_SECTION gCS;  
   
  We will call this a critical section object.  
   
  Code that is considered sensitive (or critical) by a given thread is surrounded by a call to the EnterCriticalSection and LeaveCriticalSection functions, making reference to the critical section object gCS (for there may be others):  
 
  EnterCriticalSection(gCS);
.
.
.
LeaveCriticalSection(gCS);
 
   
  The call EnterCriticalSection(gCS) says, in effect: "May I have permission from gCS to execute the upcoming critical code?" The call LeaveCriticalSection(gCS) says to gCS: "I am finished executing critical code. You may now give permission to other threads to execute their critical code."  
   
  The EnterCriticalSection function checks to see if some other thread has already entered a critical section of code associated with this critical section object. If not, then the thread is allowed to execute its critical code or more properly, not prevented from doing so. If so, then the requesting thread is suspended, but a note is made of the request. This is why the critical section object is a data structure it needs to make notes.  
   
  When the LeaveCriticalSection function is called by the thread that currently has permission to run its critical code associated with this critical section object, the system can check to see which thread is next in line, waiting for the critical section object to become free. It can then wake that thread, which will resume execution (during its time slices, of course).  
Page 199
   
  Synchronization Using Kernel Objects  
   
  Many kernel objects, including process objects, thread objects, file objects, mutex objects, semaphore objects, file change notification objects, and event objects, can exist in one of two states: signaled or nonsignaled. It may be helpful to think of these objects as having a lightbulb attached to them, as in Figure 12-1. When the light is on, the object is signaled; when it is off, the object is nonsignaled.  
   
  0199-01.gif  
   
  Figure 12-1.
A signaled kernel object
 
   
  To illustrate, when a process is created, its kernel object is nonsignaled. When the process terminates, its kernel object becomes signaled. Similarly, running threads are nonsignaled (that is, their objects are nonsignaled), but become signaled when they terminate. In fact, mutex objects, semaphore objects, event objects, file change objects, and waitable timer objects exist exclusively for the purpose of signaling!  
   
  The point of all this signaling is that it is possible for a thread to suspend itself until a specified object becomes signaled. For instance, a thread in one process can suspend itself until another process terminates simply by waiting for the other process's kernel object to become signaled.  
   
  The functions WaitForSingleObject and WaitForMultipleObjects allow a thread to suspend itself until the specified object (or objects) is signaled. We will confine our discussion to WaitForSingleObject, whose declaration is:  
 
  DWORD WaitForSingleObject(
   HANDLE hHandle,      // handle to object to wait for
   DWORD dwMilliseconds // time-out interval in milliseconds
);
 
Page 200
   
  or, in VB:  
 
  Declare Function WaitForSingleObject Lib "kernel32" _
Alias "WaitForSingleObject" ( _
   ByVal hHandle As Long, _
   ByVal dwMilliseconds As Long _
) As Long
 
   
  The hHandle parameter is the handle of the object for which notification of the signaled state is requested, and the dwMilliseconds is the time that the calling thread is willing to wait. If dwMilliseconds is 0, the function will return immediately with the current signal status of the object. This is how to test the state of an object. The parameter can also be set to the symbolic constant INFINITE (= -1), in which case the calling thread will wait indefinitely.  
   
  The WaitForSingleObject function puts the calling thread to sleep until the function returns a value. The possible return values are:  
 
  WAIT_OBJECT_0
The object is signaled.
 
 
  WAIT_TIMEOUT
The sleep interval specified by dwMilliseconds has elapsed and the object is still nonsignaled.
 
 
  WAIT_ABANDONED
In the case of a mutex object (only), the mutex was not released by the thread that owned it before that thread terminated.
 
 
  WAIT_FAILED
The function has failed.
 
   
  Example: Waiting for an Application to Terminate  
   
  Let us try an example. We want to write a function that will cause a VB application to suspend itself until another application terminates. The other application is identified by its EXE name, which, for this example, is findtext.exe. (Of course, you will probably need to change this when you try the example yourself.)  
   
  Along with OpenProcess and WaitForSingleObject, we need the following return values for WaitForSingleObject:  
 
  Public Const WAIT_FAILED = &HFFFFFFFF
Public Const WAIT_OBJECT_0 = 0
Public Const WAIT_TIMEOUT = &H102
 
   
  The WaitForAppToQuit function shown in the following code uses some  
   
  functions that we defined in Chapter 11, Processes (and are defined  
   
  in rpiAPI.bas on the CD): GetWinNTProcessID and  
   
  ProcHndFromProcIDSync. Note that we need to be a bit careful with  
   
  error return codes so that we don't use a code returned by  
   
  WaitForSingleObject.  
Page 201
 
  Public Function WaitForAppToQuit (sEXEName As String, sFQEXEName As String, _
   lWaitSeconds As Long) As Integer

' Suspend execution until app with specified EXE quits
' or until lWaitSeconds seconds has passed
'
' If sFQEXEName is not empty, use it. Otherwise, use sEXEName.
' Returns 0 on success
' Returns 1 if no process with the specified EXE
' Returns 2 if both sFQName and sName are empty
' Returns 3 if error getting process list
' Returns 4 if cannot get handle to existing process
' Otherwise returns the return value of WaitForSingleObject

Dim lret As Long
Dim hProcessID As Long
Dim hProcess As Long
Dim cMatches As Long
Dim sEXE As String

hProcessID = GetWinNTProcessID(sFQEXEName, sEXEName, cMatches)

If hProcessID <= 0 Then
   ' Error--translate to error code for this function
   If hProcessID = 0 Then
      WaitForAppToQuit = 1    ' no such process
   ElseIf hProcessID = -1 Then
      WaitForAppToQuit = 2    ' no EXE specified
   ElseIf hProcessID = -2 Then
      WaitForAppToQuit = 3    ' error getting process list
   End If
   Exit Function
End If

' Get handle from process ID
hProcess = ProcHndFromProcIDSync(hProcessID)

If hProcess = 0 Then
   WaitForAppToQuit = 4    ' error getting process handle
   Exit Function
End If

' Wait
WaitForAppToQuit = WaitForSingleObject(hProcess, 1000& * lWaitSeconds)

CloseHandle hProcess

End Function
 
   
  Here is some code to try this function out:  
 
  Public Sub WaitForAppToQuitExample()

Dim t As Long
t = Timer
 
 

Page 202
 
  lret = WaitForAppToQuit ("findtext.exe", "", 10&)
Debug.Print "Return:" & lret

Select Case lret
Case WAIT_TIMEOUT
   Debug.Print "timeout: " & Timer - t
Case WAIT_OBJECT_0
   Debug.Print "resume: " & Timer - t
Case WAIT_FAILED
   Debug.Print "Failed: " & GetAPIErrorText(Err.LastDllError)
End Select

End Sub
 
   
  Before running this code, change the EXE name to one that reflects a running application on your system. If you let the function run to completion, the output will look something like the following:  
 
  Return: 258
timeout: 10.51171875
 
   
  However, if you start the code and immediately terminate the Findtext.exe application, the output will be something like the following:  
 
  Return: 258
resume: 2.3046875
 
   
  Mutexes  
   
  A mutex is a kernel object that can be used to synchronize threads running in different processes. A mutex may or may not be owned by some thread. If a mutex is owned by a thread, then the mutex is nonsignaled. If the mutex is not owned by any thread, the mutex is signaled. In other words, for a mutex, owned is equivalent to nonsignaled.  
   
  If a mutex is not owned by any thread, the first thread that calls WaitForSingleObject gains ownership of the mutex, in which case the mutex immediately becomes nonsignaled.  
   
  In a sense, a mutex is like a baton that can be ''picked up" by any thread on a first-come-first-served basis.  
   
  The point is that when a thread tries to acquire a mutex (pick up the baton) that is already owned (nonsigned) by calling WaitForSingleObject, the thread is suspended until the mutex becomes available, which happens when the owner of the mutex releases it (drops the baton).  
   
  Mutexes are created by calling the CreateMutex function:  
 
  HANDLE CreateMutex(
   LPSECURITY_ATTRIBUTES lpMutexAttributes, // pointer to security attributes
 
 

Page 203
 
     BOOL bInitialOwner, // flag for initial ownership
   LPCTSTR lpName      // pointer to mutex-object name
);
 
   
  or, in VB:  
 
  Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" ( _
   lpMutexAttributes As SECURITY_ATTRIBUTES, _
   ByVal bInitialOwner As Long, _
   ByVal lpName As String _
) As Long
 
   
  or, to pass NULL in the first parameter:  
 
  Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" ( _
   ByVal lpMutexAttributes As Long, _
   ByVal bInitialOwner As Long, _
   ByVal lpName As String _
) As Long
 
   
  The first parameter relates to security and can be set to NULL to prevent the handle from being inherited by child processes. The bInitialOwner parameter specifies whether the calling thread will initially own the mutex (TRUE) and therefore the mutex will be nonsignaled, or whether the mutex will initially be unowned (FALSE) and therefore signaled.  
   
  The lpName parameter is the name of the mutex. This parameter can be set to NULL if no name is desired. When a mutex is named, it can be shared among processes. Once a named mutex is created by one process, a thread in another process can call CreateMutex or OpenMutex, using the same name. In either case, the system will simply pass the calling thread a handle to the original mutex. Another way to share a mutex is to call DuplicateHandle.  
   
  A mutex must be shared in order to work across processes. The reason is simple: to acquire ownership of a mutex (WaitForSingleObject) or to release that ownership, a thread must have a handle to the mutex! A thread releases a mutex by calling ReleaseMutex:  
 
  BOOL ReleaseMutex(
   HANDLE hMutex // handle to mutex object
);
 
   
  or, in VB:  
 
  Declare Function ReleaseMutex Lib "kernel32" Alias "ReleaseMutex" ( _
   ByVal hMutex As Long _
) As Long
 
   
  Since mutexes are owned by threads, the problem arises as to what happens if the thread that owns a mutex terminates without first releasing the mutex. The solution is that the system frees the mutex (returning it to signaled state). The thread that called WaitForSingleObject using this mutex receives the WAIT_ABANDONED  
Page 204
   
  return value, which probably indicates that something was wrong with the now-deceased owning thread. It is then up to the waiting thread to decide whether or not to proceed as usual.  
   
  Example: Alternating Counters Using Mutexes  
   
  To demonstrate the use of mutexes, we will create two small VB applications. Each application counts up to 5 and then pauses while the other application counts to 5. This alternates back and forth. Figure 12-2 shows the main window. (This example will illustrate events as well.)  
   
  0204-01.gif  
   
  Figure 12-2
Illustrating mutexes and events
 
   
  To run this example, run both ThreadSync1.exe and ThreadSync2.exe and place the forms side by side. Then choose the Mutexes option button on each form. Click on each of the Create Mutex command buttons and then on each of the Start Counting buttons. When you get bored, click on both Stop Counting buttons.  
   
  The complete code for these almost identical projects is on the CD. The heart of the projects is the following code, which is essentially a loop that counts to 5, releases the mutex, then waits for the mutex to become free again. That's it. Note that each application must release the mutex when done or else the other process will get caught in its wait cycle. The code for this is:  
 
  Private Sub cmdStartCountingMutex_Click()

Dim c As Long
Dim lret As Long
 
 

Page 205
 
  gbStopCounting = False

Do
   c = c + 1
   txtCount.Text = c
   Delay 0.5
   If (c Mod 5) = 0 Then
      ' Release mutex so other process can get it
      lret = ReleaseMutex(hMutex)
      ' Then wait up to 1/2 minute for the mutex to become free again
      lret = WaitForSingleObject (hMutex, 1000 * 30)
   End If
Loop Until gbStopCounting

' To free other process
lret = ReleaseMutex(hMutex)

End Sub
 
   
  Events  
   
  Events are used to signal that a certain task has been completed. Unlike mutexes, however, they are not owned by any thread.  
   
  For example, Thread A creates an event using CreateEvent and sets the event's state to nonsignaled. Thread B gets a handle to the event by calling OpenEvent and then calls WaitForSingleObject to suspend itself until Thread A completes a certain task and sets the event object to the signaled state. When this happens, the system wakes Thread B, which then knows that the task has been completed by Thread A.  
   
  The CreateEvent declaration is:  
  HANDLE CreateEvent       LPSECURITY_ATTRIBUTES lpEventAttributes,  // pointer to security attributes       BOOL bManual Reset,                       // flag for manual-reset event       BOOL bInitialState,                       // flag for initial state       LPCTSTR lpName                            // pointer to event-object name     );    
   
  This function returns a handle to the event object that it creates. The first parameter determines whether or not the handle is inheritable by child processes. If lpEventAttributes is NULL, then the handle cannot be inherited.  
   
  If the bManualReset parameter is TRUE, when the event is signaled, it remains signaled (unlike a mutex, for example). This implies that all threads waiting for the event to become signaled will be waked by the system. Such an event is referred to as a manual-reset event, because a waking thread must reset the event to the nonsignaled state if it so desires. On the other hand, if bManualReset is FALSE, then the system automatically unsignals the event after waking the first  
Page 206
   
  thread that is waiting for the event to become signaled. Thus, in this case, only one event is waked, as is the case with mutexes. In this case, the event is called an auto-reset event.  
   
  The bInitialState parameter determines the initial state (TRUE for signaled, FALSE for nonsignaled) of the event. Finally, the lpName parameter can be set to the name of the event. This provides a method for event sharing, via the OpenEvent function, for instance.  
   
  We can translate CreateEvent into VB as follows:  
 
  Declare Function CreateEvent Lib "kernel32" Alias "CreateEventA" ( _
   lpEventAttributes As SECURITY_ATTRIBUTES, _
   ByVal bManualReset As Long, _
   ByVal bInitialState As Long, _
   ByVal lpName As String _
) As Long
 
   
  Alternatively, if we do not want to deal with security issues, we can set lpEventAttributes to NULL (0&), in which case we want the following declaration:  
 
  Declare Function CreateEvent Lib "kernel32" Alias "CreateEventA" ( _
   ByVal lpEventAttributes As Long, _
   ByVal bManualReset As Long, _
   ByVal bInitialState As Long, _
   ByVal lpName As String _
) As Long
 
   
  As with other handles, an event handle should be closed using the CloseHandle function.  
   
  The OpenEvent function is:  
 
  HANDLE OpenEvent(
   DWORD dwDesiredAccess, // access flag
   BOOL bInheritHandle, // inherit flag
   LPCTSTR lpName // pointer to event-object name
);
 
   
  where the dwDesiredAccess parameter can take one of three values:  
 
  EVENT_ALL_ACCESS
Gives full access to the event.
 
 
  EVENT_MODIFY_STATE
Enables use of the event handle in the SetEvent and ResetEvent functions, so that the calling process can change the state of the event (but nothing else). This is important for manual-reset events.
 
 
  SYNCHRONIZE
Enables use of the event handle in any of the "Wait" functions (such as WaitForSingleObject) to wait for the event's state to be signaled.
 
Page 207
   
  The OpenEvent function can be declared in VB as:  
 
  Declare Function OpenEvent Lib "kernel32" Alias "OpenEventA" ( _
   ByVal dwDesiredAccess As Long, _
   ByVal bInheritHandle As Long, _
   ByVal lpName As String _
) As Long
 
   
  The following declarations are also used with events:  
 
  Declare Function SetEvent Lib "kernel32" (ByVal hEvent As Long) As Long
Declare Function ResetEvent Lib "kernel32" (ByVal hEvent As Long) As Long
Declare Function PulseEvent Lib "kernel32" (ByVal hEvent As Long) As Long
 
   
  Each of these takes an event handle as an argument. SetEvent sets the event's state to signaled, and ResetEvent unsignals the event. The PulseEvent function calls SetEvent to release waiting threads, and then calls ResetEvent to reset the event to its unsignaled state.  
   
  Example: Alternating Counters Using Events  
   
  The mutex example in Figure 12-2 also illustrates how the same goal can be achieved using two events. The main code is:  
 
  Private Sub cmdStartCountingEvent_Click()

Dim c As Long
Dim lret As Long

gbStopCounting = False

Do
   c = c + 1
   txtCount.Text = c
   Delay 0.5
   If (c Mod 5) = 0 Then
      ' Pulse event to signal other process
      lret = PulseEvent (hEvent1)
      ' and wait up to 30 seconds for the foreign event to be signaled
      lret = WaitForSingleObject (hEvent2, 1000 * 30)
   End If
Loop Until gbStopCounting

' To awake other process
lret = SetEvent (hEvent1)

End Sub
 
   
  In this case, each application pulses its own event, signaling to the other application that its counting-to-five task is complete. Then it waits for a corresponding signal from the other application.  
   
  Once you have both ThreadSync.exe executables running, just click on the Create Event buttons in each project to create their respective events. Then click on each  
Page 208
   
  application's Open Event buttons to open the foreign event. Finally, click on the Start Counting buttons to start the counting process. When you get bored, click on the Stop Counting buttons.  
   
  Semaphores  
   
  Semaphores are used for resource counting. A semaphore is signaled when its resource count is positive and nonsignaled when its resource count is 0. In general, a positive resource count indicates that resources are available.  
   
  Accordingly, after a thread calls WaitForSingleObject and passes the handle of a semaphore, the system checks the resource count for that semaphore. If the count is positive (the semaphore is signaled), the WaitForSingleObject function returns, indicating that resources are available to the process (which can proceed to claim those resources). If the resource count is 0 (semaphore is nonsignaled), the system puts the thread to sleep until the resource count becomes positive, which would generally happen when some other process frees this particular resource. For this to work, when a thread frees the resource, it must also release the semaphore, in which case the system increments the resource count.  
   
  Thus, semaphores are good for sharing limited resources. For instance, suppose we had three applications that each wanted to print, but the computer has only two parallel ports. By setting up a semaphore with initial resource count equal to 2, we can force the applications to request printing only when there is an available parallel port.  
 
  Waiting Can Be Tricky  
   
  We should conclude this chapter by mentioning that sometimes waiting can be tricky. The problem stems from the fact that some threads need to be ready at a moment's notice to process messages from other applications. We will discuss messages in Chapter 16, Windows Messages, but suffice it to say here that some messages are broadcast to every window in the system. Now, if the thread that manages messages for a particular window is waiting for a signal from some mutex or event, for instance, then it cannot process incoming messages. This will cause a deadlock because the receiving application cannot process the message and the sending application cannot continue until it gets a response to its message!  
   
  The solution to this problem is to not send threads that are responsible for message processing into a wait state by calling WaitForSingleObject. Fortunately, this is not normally a problem in VB because, as VB programmers, we do not generally write code for threads that process messages.  



WIN32 API Programming with Visual Basic
Win32 API Programming with Visual Basic
ISBN: 1565926315
EAN: 2147483647
Year: 1999
Pages: 31
Authors: Steven Roman

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net