From a user perspective; it would seem obvious to call the method IncrementCounter() directly from the other thread (_thread)。 However; hidden in the implementation of Invoke() is a producer/consumer implementation。 The producer is the Invoke() method; which adds a delegate that needs to be called to a queue。 The consumer is the Windows。Forms。Form class; which periodically checks its Invoke() queue and executes the delegates contained within。 In a nutshell; a producer/consumer implementation is nothing more than a handoff of information from one thread to another thread。 This is effective because the producer and consumer are separate and manage their own concerns。 The only mon information between the producer and consumer is a queue; or list; which is synchronized and contains information of interest to both the producer and consumer。 Implementing a Generic Producer/Consumer Architecture The architecture implemented by Windows。Forms is elegant and self…containing。 You can imple ment a generic producer/consumer architecture using a delegate; as shown in the following source code。 In this case; we use the System。Action class; which encapsulates a Sub that doesn’t take any parameters (it is shorthand for Public Delegate Sub Action())。 …………………………………………………………Page 387…………………………………………………………… C HA P TE R 1 3 ■ L E AR N IN G AB O U T M U L T IT HR E AD IN G 365 Imports System。Threading Class ThreadPoolProducerConsumer Private _queue As Queue(Of Action) = New Queue(Of Action)() Private Sub QueueProcessor(Byval obj As Object) Monitor。Enter(_queue) Do While _queue。Count = 0 Monitor。Wait(_queue; …1) Loop Dim action As Action = _queue。Dequeue() Monitor。Exit(_queue) ThreadPool。QueueUserWorkItem(AddressOf QueueProcessor) action() End Sub Public Sub New() ThreadPool。QueueUserWorkItem(AddressOf QueueProcessor) End Sub Public Sub Invoke(ByVal toExec As Action) Monitor。Enter(_queue) _queue。Enqueue(toExec) Monitor。Pulse(_queue) Monitor。Exit(_queue) End Sub End Class ThreadPoolProducerConsumer has a single public method; Invoke(); which is used in the same fashion as the Windows。Forms Invoke() method。 What makes the generic producer/consumer work is its use of the Monitor synchronization class。 To understand how Monitor works in the producer/consumer context; consider the overall producer/consumer implementation。 The consumer thread (QueueProcessor()) executes constantly; waiting for items in the queue (_queue)。 To check the queue; the Monitor。Enter() method is called; which says; “I want exclusive control for a code block that ends with the method call Monitor。Exit()。” To check the queue; a Do While loop is started。 The loop waits until there is something in the queue。 The thread could execute constantly; waiting for something to be added; but while the thread is looping; it has control of the lock。 This means a producer thread cannot add anything to the queue。 The consumer needs to give up the lock; but also needs to check if anything is available in the queue。 The solution is to call Monitor。Wait(); which causes the consumer thread to release the lock and say; “Hey; I’m giving up the lock temporarily until somebody gives me a signal to continue processing。” When the consumer thread releases its lock temporarily; it goes to sleep waiting for a pulse。 …………………………………………………………Page 388…………………………………………………………… 366 CH AP T E R 1 3 ■ L E A R N I N G A B OU T M U L T I TH R E A DI N G The producer thread (Invoke()) also enters a protected block using the Monitor。Enter() method。 Within the protected block; an item is added to the queue using the Enqueue() method。 Because an item has been added to the queue; the producer thread sends a signal using the Monitor。Pulse() method to indicate an item is available。 This will cause the thread that gave up the lock temporarily (the consumer thread) to wake up。 However; the consumer thread executes when the producer thread calls Monitor。Exit()。 Until then; the consumer thread is in ready…to…execute mode。 In the simplest case of this implementation; a single thread would constantly execute QueueProcessor()。 An optimization is to create and use a thread pool。 A thread pool is a collec tion of ready…to…execute threads。 As tasks arrive; threads are taken from the pool and used to execute the tasks。 Once the thread has pleted executing; it is returned to the thread pool in ready…to…execute mode。 In the ThreadPoolProducerConsumer constructor; the method ThreadPool。 QueueUserWorkItem() uses thread pooling to execute the method QueueProcessor()。 In the implementation of QueueProcessor(); the method ThreadPool。QueueUserWorkItem() is called again before calling the delegate。 The result is that one thread is always waiting for an item in the queue; but there may be multiple threads executing concurrently; processing items from the queue。 Using the generic producer/consumer is nearly identical to using the Windows。Forms Invoke() method。 The following is a sample implementation。 Imports System。Threading Public Class TestProducerConsumer Delegate Sub TestMethod() Sub Method() Console。WriteLine(〃Processed in thread id (〃 & _