按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
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 (〃 & _
Thread。CurrentThread。ManagedThreadId & 〃)〃)
End Sub
Public Sub TestSimple()
Dim producer As ThreadPoolProducerConsumer = _
New ThreadPoolProducerConsumer()
Console。WriteLine(〃Sent in thread id (〃 & _
Thread。CurrentThread。ManagedThreadId()& 〃)〃)
producer。Invoke(AddressOf Method)
End Sub
End Class
The TestSimple() method instantiates the ThreadPoolProducerConsumer type。 Then the
Invoke() method is called using the delegate TestMethod; which executes the Method() method。
■Note The Thread。ManagedThreadId property uniquely identifies the managed thread to which it belongs。
…………………………………………………………Page 389……………………………………………………………
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 367
Using an Asynchronous Approach
Using asynchronous techniques means to perform a task; such as read a file or database result;
and then rather than wait for the results; let some other code handle the results。 The asynchro
nous interaction is an example of the producer/consumer architecture; where the consumer
makes a request of the producer and carries on until it gets a response to that request。
The asynchronous technique used throughout the API is consistent and is easily
demonstrated by reading a file asynchronously。 In Chapter 10; you learned how to read a file or
console stream using synchronous techniques。 You could just as well have read that file or
stream asynchronously。
You would use asynchronous techniques when you don’t want to wait around for the task
to plete。 To read a file asynchronously; the following source code is used。
Imports System。IO
Imports System。Text
Friend Module TestAsynchronousFile
Dim dat