CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Grant Killian's Blog

No, this has nothing to do with beer -- but maybe it should?

Asynchronous Behaviour via .Net Delegates

In the VB class I’m currently teaching at nights for ITPro, one of the students asked for a “very good example of how and why I might use a delegate.”

Answering this question without getting into topics way beyond the course curriculum is very tricky.  I considered showing a direct threading technique, but most threading topics are beyond the scope of the class; I finally decided on asynchronous delegates (which still manipulate threads, just not so obviously) since it’s closest to our class material -- plus, I had a sample handy from a recent project.  It’s a vast subject, and this is a very brief treatment . . . but I hope it serves as an example of how and why one might use a delegate.   For a more comprehensive look at this sort of thing, check out this MSDN article; for detailed delegate insight, check out some of Don Box’s blogs (he’s Microsoft’s Mr. CLR!).

The first programs we write in VB.Net are all very simple and run in linear fashion; statement one executes, followed by statement two, and so on.  Most serious programs, for performance purposes, outgrow this paradigm and adopt asynchronous behaviour in certain situations.  When a process is going to take exceptionally long (or long enough for the user to perceive –- say one second or longer), executing those code statements asynchronously is an attractive solution.  We can use delegates to accomplish it.

Let’s take a class that handles database querying named “ReadDatabase” and a method with time-consuming database work named “CountStuff”:
Class ReadDatabase
    Public Function CountStuff( byval SQL as String ) as integer
        dim intOut as integer = new Random().Next( 0, 10000 )
        System.Threading.Thread.Sleep( 5000 )
        return intOut
    End Function
End Class

Bare with me here . . . we really aren’t querying a database; we’re using the Thread.Sleep() method to pause the current thread 5 seconds (5000 milliseconds).  This simulates a time consuming database task and the Random number serves as our return value.  This is just a mock object.

Now we declare our Delegate:
Public Delegate Function DBWorkDelegate( byval strSQL as String  ) as integer

A Delegate is just a type-safe function pointer; there’s no implementation (sort of like an Interface).  This parameter list and return type create a contract that the Delegate enforces on any functions it points to; it can't point to anything else.

Now let’s add a form to our application and add a button and listbox to it; let’s add the following code to the button’s click event:
Dim obj As new ReadDatabase()
Dim cb As New AsyncCallback( AddressOf AllDone )
Dim del As New DBWorkDelegate( AddressOf obj.CountStuff )
del.BeginInvoke( "SELECT * FROM tblData", cb, del )
ListBox1.Items.Add( "Starting Thread: " & appdomain.GetCurrentThreadId )

Whew, this is a lot of new stuff (for our class at least!).  Taking it line by line:
We declare and instantiate a ReadDatabase object.
We declare and instantiate an AsyncCallback object.  This object holds a reference to the method that should execute when the asynchronous work is done (our final step is to add this AllDone method).
We declare and instantiate an instance of our DBWorkDelegate Delegate, passing it the method to execute asynchronously.
We call the BeginInvoke method on that delegate, passing it the arguments our CountStuff method expects as well as the AsyncCallBack object and the DBWorkDelegate instances.  This will spawn a new thread for the data-intensive work to be done on.
Finally, we add an item to our listbox for debugging purposes; the appDomain is like a meta-thread for .Net programs (somewhere between a Process and Thread), we use it to get a handle on the ThreadID we’re currently executing on.  When our program runs, we’ll find that there are multiple threads being used behind the scenes in our Delegate invocation.

Almost done.  We need to add the AllDone method as our callback recipient:
    Sub AllDone( ByVal ar As IAsyncResult )
          Dim del As DBWorkDelegate
          del = CType( ar.AsyncState, DBWorkDelegate )
          ListBox1.Items.Add( "Finished Thread: " & appdomain.GetCurrentThreadId )
          MessageBox.Show( del.EndInvoke( ar ) & " records found" )
    End Sub

This method will fire when our asynchronous work is done (courtesy of the .Net Delegate plumbing).  Note that the callback method must accept a type of IAsyncResult object, and that we convert it to the specific delegate type we’re using.  We also add an item to our listbox displaying the ThreadID of the asynchronous work.  Finally, to get the return value from our CountStuff method, we call EndInvoke on our delegate; at this point our asynchronous work is done.

Try it yourself; you’ll find the following:
After you click the button, the listbox displays something like “Starting Thread: 3724”.  Also notice that you can manipulate your form – it isn’t blocking while the database work takes place (“blocking” is the term used when program execution waits for control to return from a lengthy operation); you can resize your form and, *try this*, click your button several times. 
Once the asynchronous work is done, you’ll see a dialog box with the random number in it and a new line in the listbox saying “Finished Thread: 2516”.

If you click the button multiple times in quick succession, you’ll see the starting thread is always the same but the asynchronous thread is different; this can lead to a multithreading conversation, but I’ll save it for another time. 

I originally concluded the post here, but kind Dutchman Peter van Ooijen pointed out a threading issue with the callback method.  My family happens to be Dutch and we have a funny phrase: if you ain't Dutch you ain't much -- Peter sure is much help in this case!   The fact that different ThreadIDs display in the listbox is a sure fire indication that we've got multiple threads interacting with the User Interface -- this is BAD because it can lead to very hard to reproduce and find bugs).  Only the UI-thread should interract with UI controls; our previous example will work most of the time, but is actually not “thread-safe.” 

Fortunately, .Net provides a convenient means to “merge“ our callback function into the UI thread.  If we replace our callback method with the following:

        If me.InvokeRequired then 'if we have to merge into the UI thread...
            Dim objArgs( 0 ) as Object
            objArgs( 0 ) = ar
            me.Invoke( new AsyncCallback( AddressOf AllDone ), objArgs  )
        Else
            Dim del As DBWorkDelegate
            del = DirectCast(ar.AsyncState, DBWorkDelegate)
            ListBox1.Items.Add( "Finished Thread: " & appdomain.GetCurrentThreadId )
            MessageBox.Show( del.EndInvoke( ar ) & " records found" )
        End If

The .Net Form object contains an InvokeRequired method that determines our thread condition; we pass the callback function “AllDone“ as a delegate in the Me.Invoke() method call, and Me.Invoke() ensures our callback method runs in a thread-safe manner.  When InvokeRequired returns false (meaning, we're on the same thread), we can handle our IAyncResult as we did before.  If you run this example, you'll notice that the ThreadIDs are now the same for the “Startng Thread“ and the “Finished Thread.“  You can read Peter van Ooijen's treatment of this subject in a web service example here.

This is a very practical example of Delegates in action; I don’t know if it qualifies as a “very good” example . . . I’ll have to let you be the judge of that.

Happy .Netting



Comments

Grant said:

You're absolutely correct, I reworked the CallBack method; thanks!
# August 25, 2003 2:57 AM

Daniel Turini said:

It's important to notice that the thread where the async delegate will be running on is borrowed from the thread pool. This pool have a size of 25 threads per processor. And it's the same pool used by ASP.NET to handle requests. On Windows Forms this may be fine, because the pool is often more than enough, but don't use this on ASP.NET pages or soon you'll have a problem...
# August 25, 2003 6:49 AM

Torrance L. Ford said:

What if the new thread originates in a class and my callback routine communicates with the UI via an event? When I rearrange the logic of this example in this way, I once again have the class / event on one a thread different from the main UI thread. Hope this makes sense. Thanks
# May 25, 2004 3:12 PM

sheir said:

Hi,
New to Async with ASP.NET 1.1, and wondered how to do the above in an ASP.NET application if webcontrols do not have  the InvokeRequired property.
I think I read somewhere that it does not matter for ASP.NET, that any thread can update a webcontrol (say a mulitline textbox) but not sure how true that is.

Any help?
sheir
# July 17, 2006 3:28 PM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Check out Devlicio.us!