Unfortunately, the universe, being very busy and unaccustomed to paying attention to individuals, had managed to replace Peter's boss's delegate with its own. This was an unintended side effect of making the delegate fields public in Peter's Worker class. Likewise, if Peter's boss got impatient, he could decide to fire Peter's delegates himself (which is just the kind of rude thing that Peter's boss was apt to do): ' Peter's boss taking matters into his own hands If Not (peter.completed Is Nothing) Then peter.completed() Peter wanted to make sure that neither of these could happen. He realized that he needed to add registration and unregistration functions for each delegate so that listeners could add or remove themselves but couldn't clear the entire list or fire Peter's events. Instead of implementing these functions himself, Peter used the event keyword to make the Visual Basic .NET compiler build these methods for him: Class Worker ... Public Event started As WorkStarted Public Event progressing As WorkProgressing Public Event completed As WorkCompleted End Class Peter knew that the event keyword erected a property around a delegate, allowing only clients to add or remove themselves (using the AddHandler and RemoveHandler methods, if the client was written in Visual Basic .NET), forcing his boss and the universe to play nicely , and he would be thrilled if he could simply use the following code: Shared Sub Main() Dim peter As Worker = New Worker() Dim theboss As Boss = New Boss() AddHandler peter.completed, AddressOf boss.WorkCompleted AddHandler peter.started, AddressOf Universe.WorkerStartedWork AddHandler peter.completed, AddressOf Universe.WorkerCompletedWork peter.DoWork() Console.WriteLine("Main: worker completed work") Console.ReadLine End Sub Unfortunately for Peter, he had just run up against a limitation in Visual Basic .NET: Events in Visual Basic .NET could not expose a return type; they had to be Subs, and never Functions. The preceding code worked beautifully for the started and progressing events, but threw a compilation error on completed. Peter had two choices: to eliminate the return value from completed, or control registrations to the delegate form of completed himself. Peter chose to use the multicast delegate form to register multiple listeners to a single event: Shared Sub Main() Dim peter As Worker = New Worker() Dim theboss As Boss = New Boss() ' Get handlers to listeners for completed event Dim d1, d2 As WorkCompleted d1 = AddressOf theboss.WorkCompleted d2 = AddressOf Universe.WorkerCompletedWork ' Use Shared Combine method of Delegate ' class to assign both to delegate peter.completed = _ CType(System.Delegate.Combine(d1, d2), WorkCompleted) ' Use AddHandler method for started Event AddHandler peter.started, AddressOf Universe.WorkerStartedWork ' Do work peter.DoWork() Console.WriteLine("Main: worker completed work") Console.ReadLine End Sub Harvesting All ResultsAt this point, Peter breathed a sigh of relief. He had managed to satisfy the requirements of all his subscribers without having to be closely coupled with the specific implementations . However, he noticed that although both his boss and the universe provided grades of his work, Peter was receiving only one of the grades. In the face of multiple subscribers, he really wanted to harvest all of their results. So, he reached into his delegate and pulled out the list of subscribers so that he could call each of them manually: Public Sub DoWork() ... Console.WriteLine("Worker: work completed") If Not (completed Is Nothing) Then Dim wc As WorkCompleted For Each wc In completed.GetInvocationList() Dim grade As Integer = wc() Console.WriteLine("Worker grade=" & grade.ToString()) Next End If End Sub Asynchronous Notification: Fire and ForgetIn the meantime, his boss and the universe had been distracted with other things, and this meant that the time it took them to grade Peter's work had been greatly expanded: Class Boss Public Function WorkCompleted() As Integer System.Threading.Thread.Sleep(3000) Console.WriteLine("Better...") Return 6 ' out of 10 End Function End Class Class Universe Shared Function WorkerCompletedWork() As Integer System.Threading.Thread.Sleep(4000) Console.WriteLine("Universe is pleased with worker's work") Return 7 ' out of 10 End Function End Class Unfortunately, because Peter was notifying each subscriber one at a time, waiting for each to grade him, these notifications now took up quite a bit of his time when he should have been working. So, he decided to forget the grade and just fire the event asynchronously: Public Sub DoWork() ... Console.WriteLine("Worker: work completed") If Not(completed Is Nothing) Then Dim wc As WorkCompleted For Each wc In completed.GetInvocationList() wc.BeginInvoke(Nothing, Nothing) Next End If End Sub Asynchronous Notification: PollingThis clever trick allowed Peter to notify the subscribers while letting him get back to work immediately, letting the process thread pool invoke the delegate. Over time, however, Peter found that he missed the feedback on his work. He knew that he did a good job and appreciated the praise of the universe as a whole (if not his boss specifically ). So, he fired the event asynchronously but polled periodically, looking for the grade to be available: Public Sub DoWork() ... Console.WriteLine("Worker: work completed") If Not (completed Is Nothing) Then Dim wc As WorkCompleted For Each wc In completed.GetInvocationList() Dim res As IAsyncResult = wc.BeginInvoke(Nothing, Nothing) While Not res.IsCompleted System.Threading.Thread.Sleep(1) End While Dim grade As Integer = wc.EndInvoke(res) Console.WriteLine("Worker grade= " & grade.ToString()) Next End If End Sub Asynchronous Notification: DelegatesUnfortunately, Peter was back to the very thing he wanted his boss to avoid with him in the beginning: looking over the shoulder of the entity doing the work. So, he decided to employ another delegate as a means of notification when the asynchronous work was completed, allowing him to get back to work immediately but still be notified when his work had been graded: Public Sub DoWork() ... Console.WriteLine("Worker: work completed") If Not (completed Is Nothing) Then Dim wc As WorkCompleted For Each wc In completed.GetInvocationList() Dim res As IAsyncResult = wc.BeginInvoke(New AsyncCallback( _ AddressOf WorkGraded), wc) Next End If End Sub Public Sub WorkGraded(ByVal res As IAsyncResult) Dim wc As WorkCompleted = CType(res.AsyncState, WorkCompleted) Dim grade As Integer = wc.EndInvoke(res) Console.WriteLine("Worker grade= " & grade.ToString()) End Sub |