Charles,
The rules of encapsulation states, that you do not pass the Event itself
to object1 & object2, as the Event itself is an implementation detail of
the Worker class.
You could pass the instance of the Worker class to object1 & object2,
however it can be easier for object1 & object2 to simply call a shared
method of the Worker class. Either way object1 & object2 would be coupled
to Worker, whether I used a shared property or a parameter would depend on
how many routines & classes I needed to pass the parameter to. If object1
& object2 "exists" only on a single thread I have been known to pass the
"Worker" class to the constructor of object1 & object2, then the any
method can gain access to the "current" "Worker" object...
If you call a shared method on the Worker class, the Worker class needs to
know the instance of the Worker class that is associated with the current
thread. You can use the Thread Static variable to associate the instance
of the Worker class with each thread, as the ThreadStatic variable causes
the shared member to be unique per thread... Alternatively you could use a
shared HashTable, where the Thread ID is the key & the Worker instance is
the value...
By encapsulating the Event within Worker you also gain more readable code,
your UI thread can call Worker.Suspend & Worker.Resume, while the Worker
thread itself would call Worker.CheckSuspend.
Here is a an example of a Worker class using a ManualResetEvent to control
suspending & resuming.
Public Class Worker
Public Delegate Sub Work()
Private ReadOnly m_work As Work
Private ReadOnly m_arg As Object
Private ReadOnly m_event As ManualResetEvent
Private ReadOnly m_thread As Thread
<ThreadStatic()> _
Private Shared m_current As Worker
Public Sub New(ByVal work As Work, ByVal arg As Object, ByVal name
As String)
m_work = work
m_arg = arg
m_event = New ManualResetEvent(True)
m_thread = New Thread(AddressOf Start)
m_thread.Name = name
m_thread.IsBackground = True
m_thread.Start()
End Sub
Private Sub Start()
m_current = Me
m_work.Invoke()
End Sub
Public Shared ReadOnly Property CurrentWorker() As Worker
Get
Return m_current
End Get
End Property
Public ReadOnly Property Arg() As Object
Get
Return m_arg
End Get
End Property
Public ReadOnly Property Name() As String
Get
Return m_thread.Name
End Get
End Property
Public Sub Suspend()
If Thread.CurrentThread Is m_thread Then
Throw New InvalidOperationException("Suspend should not be
called from the worker thread!")
End If
m_event.Reset()
End Sub
Public Sub [Resume]()
If Thread.CurrentThread Is m_thread Then
Throw New InvalidOperationException("Resume should not be
called from the worker thread!")
End If
m_event.Set()
End Sub
Public Sub WaitForTermination()
If Thread.CurrentThread Is m_thread Then
Throw New InvalidOperationException("WaitForTermination
should not be called from the worker thread!")
End If
m_thread.Join()
End Sub
Public Sub CheckSuspend()
If Not Thread.CurrentThread Is m_thread Then
Throw New InvalidOperationException("CheckSuspend should
only be called from the worker thread!")
End If
m_event.WaitOne()
End Sub
End Class
To see how it works try something like:
Private Sub Work()
For index As Integer = 1 To 50
Worker.CurrentWorker.CheckSuspend()
Debug.WriteLine(index, Worker.CurrentWorker.Name)
Dim value As Double = DirectCast(Worker.CurrentWorker.Arg,
Double)
Thread.Sleep(TimeSpan.FromSeconds(value))
Next
End Sub
Public Sub Main()
Dim worker1 As New Worker(AddressOf Work, 0.25, "worker1")
Dim worker2 As New Worker(AddressOf Work, 0.5, "worker2")
Dim worker3 As New Worker(AddressOf Work, 0.75, "worker3")
Debug.WriteLine("Pausing 5 seconds", "Main")
Thread.Sleep(TimeSpan.FromSeconds(5))
worker1.Suspend()
Debug.WriteLine("Pausing 5 seconds", "Main")
Thread.Sleep(TimeSpan.FromSeconds(5))
worker2.Suspend()
Debug.WriteLine("Pausing 5 seconds", "Main")
Thread.Sleep(TimeSpan.FromSeconds(5))
worker3.Suspend()
Debug.WriteLine("Pausing 1 seconds", "Main")
Thread.Sleep(TimeSpan.FromSeconds(1))
worker1.Resume()
worker2.Resume()
worker3.Resume()
Debug.WriteLine("Waiting for Termination", "Main")
worker1.WaitForTermination()
worker2.WaitForTermination()
worker3.WaitForTermination()
End Sub
Hope this helps
Jay
Charles Law said:
Ok. I think I'm with you now. So, in the scenario below
UI | Worker Object1 Object2
start
-----> -----> -----> Do
<----- ...
Loop
<---------|
-----> ...
pause
------>
What I am trying to show is that the UI kicks off the worker and gets
control back immediately. The worker passes control to Object1, which
passes control to Object2. Object2 loops for a bit, doing stuff,
including making recursive calls. Then, control passes back to Object1.
Later, control passes back to Object2 to do more stuff, and so on.
Then, the UI pauses the worker thread. The worker action is all taking
place down in Object2, and maybe a couple of recursions deep. Am I right
to say that when the worker passes control to Object1, it has to pass a
reference/handle to the reset event. Object1 must pass this to Object2.
So long as the recursion remains within Object2, no further parameter
need be passed, as the handle can be held in an object level variable. If
the recursion ventures into another object then that object must receive
a reference to the event.
Object2 must occasionally wait on the event, most logically within the
loop.
When the pause occurs, the event is reset by the worker, and no further
action need be taken, as this will be seen wherever the thread waits on
the event, even in other objects.
Where does the ThreadStatic field come in? As I understand it, such a
field would still only be visible to the object in which it was created,
so it does not eliminate the need for passing a reference to the other
objects in the processing chain. Is there a way to prevent the need for
passing a reference around?
Charles
<<snip>>