Atlanta .NET Regular Guys

Community Blog for two guys in Atlanta that focus on Microsoft and Community.

Quick About

This is the community blog for Brendon Schwartz and Matt Ranlett.  If you want to see their technical posts visit http://www.sharepointguys.com

Back To DevCow

Recent Posts

Tags

Email Notifications

    Archives

    Adventures in Delegates

    My previous post talks about adding an animated gif to a Winform app to act as a progress indicator. Well, I got that working and found that when my single threaded app makes the animated gif visible, then starts to do a bunch of work - the animated gif isn't animated. It pauses and waits for the work to be done.

    So now I figured I needed to offload the bulk of this work to another thread. Fortunately, VB.Net and the delegates makes this a pretty easy task. Let's take a look:

    I've got a form class which reacts to a button push from the user. A function on the form gets called to generate a list of valid storage bins and bind this list to a combo box:

    Public Sub PopulateBinsList()
    ' calculate capacity
    Dim BinCapacity As Integer
    If (IncludeRecallsCheckBox.Checked) Then
    BinCapacity = EnteredItem.WarehouseRecords(0).onHand + _
    EnteredItem.WarehouseRecords(0).onOrder + _
    EnteredItem.InRecall
    Else
    BinCapacity = EnteredItem.WarehouseRecords(0).onHand + _
    EnteredItem.WarehouseRecords(0).onOrder
    End If
    ' get valid bins
    Dim saveCursor As Cursor = Windows.Forms.Cursor.Current
    Windows.Forms.Cursor.Current = Cursors.WaitCursor
    Dim ValidBins As BinsCollection = New BinsCollection
    cboAssignBin.DataSource = ValidBins.GetValidBins(EnteredItem.AlphaCfg, _
    WarehouseNumber, _
    BinCapacity)
    cboAssignBin.DisplayMember =
    "BinNumber"
    ' this takes a while
    UpdateBinInfoLabels(
    DirectCast(cboAssignBin.SelectedItem, Bin))
    Windows.Forms.Cursor.Current = saveCursor

    End
    Sub

    The offensive line is the part where I call ValidBins.GetValidBins - it simply takes FOREVER! This is an ideal candidate for moving off to another thread. First I changed the code in my forms class to create a new object which would start the process of building a list of valid storage bins. I also needed a new function which would execute once the work had been done. This is the code I ended up with:

    Public Sub PopulateBinsList()
    ' calculate capacity
    Dim BinCapacity As Integer
    If (IncludeRecallsCheckBox.Checked) Then
    BinCapacity = EnteredItem.WarehouseRecords(0).onHand + _
    EnteredItem.WarehouseRecords(0).onOrder + _
    EnteredItem.InRecall
    Else
    BinCapacity = EnteredItem.WarehouseRecords(0).onHand + _
    EnteredItem.WarehouseRecords(0).onOrder
    End If
    ' get valid bins
    Dim saveCursor As Cursor = Windows.Forms.Cursor.Current
    Windows.Forms.Cursor.Current = Cursors.WaitCursor
    Dim BinFiller As New BinListFiller(WarehouseNumber, _
    EnteredItem, _
    BinCapacity, _
    AddressOf DatabindBinsList)
    BinFiller.BuildValidBinsCollection()
    End Sub

    Public Sub DatabindBinsList(ByRef validBins As BinsCollection)
    cboAssignBin.DataSource = validBins
    cboAssignBin.DisplayMember =
    "BinNumber"
    UpdateBinInfoLabels(
    DirectCast(cboAssignBin.SelectedItem, Bin))
    Windows.Forms.Cursor.Current = Cursors.Default
    End Sub

    Now I need to take my new DatabindBinsList and create a delegate for it. A delegate is essentially a function pointer. It allows me to create a function "signature" that I can pass around my application and use. Delegates are defined outside the class (up where you define the IMPORTS):

    Public Delegate Sub FillBinsCallback(ByRef validBins As BinsCollection)

    Notice how the delegate FillBinsCallback takes the same parameters and has the same returns as DatabindBinsList (the name of the parameter isn't important, the type and ByRef/ByVal is). This means that I can point my point my delegate to this function. This will become a little clearer as I go on. Now that I've changed my form class to expect another object to asynchronously do the work and call the DatabindBinsList subroutine when complete, I need to build that class:


    Public
    Class BinListFiller

    Private _warehouseNumber As Integer
    Private _reqCapacity As Integer
    Private _binsList As BinsCollection
    Private _targetItem As Item
    Private _callbackMethod As FillBinsCallback

    ''' <summary>
    ''' Creates a new instance of BinListFiller
    ''' </summary>
    ''' <param name="warehouseNumber"></param>
    ''' <param name="targetItem"></param>
    ''' <param name="requiredCapacity"></param>
    ''' <param name="callbackMethod"></param>
    Public Sub New(ByVal warehouseNumber As Integer, _
    ByVal targetItem As Item, _
    ByVal requiredCapacity As Integer, _
    ByVal callbackMethod As FillBinsCallback)
    MyBase.New()
    _warehouseNumber = warehouseNumber
    _reqCapacity = requiredCapacity
    _targetItem = targetItem
    _callbackMethod = callbackMethod
    End Sub

    Public Sub BuildValidBinsCollection()
    Dim ValidBins As BinsCollection = New BinsCollection
    ValidBins = ValidBins.GetValidBins(_targetItem.AlphaCfg, _
    _warehouseNumber, _
    _reqCapacity)
    _callbackMethod(ValidBins)
    End Sub
    End
    Class

    Notice how my new worker class takes as a parameter the callback method, which is a delegate. This means, when I initialize the object, I have to give it the address of a method that matches my function pointer (delegate). You can see this delegate get used in the BuildValidBinsCollection method - it calls the _callbackMethod and passes in the BinsCollection it built up. Because my _callbackMethod was passed the AddressOf DatabindBinsList, this is the function which gets executed.

    The net effect of this work - NOTHING! I've not done anything here to make the work asynchronous. All the work is still being done in a single thread. Instead what I've done is the sort of thing you might do for an object factory or something - allowing Object A cause Object B to do some work and notify Object A when it's done without Object B actually knowing anything about Object A.

    Look for the next post to reveal how I got this to the correct way - using the delegate's built in BeginInvoke functionality (actually easier than what I tried to do here)

    -- Matt Ranlett

    Posted: 01-13-2006 12:23 PM by Matt Ranlett | with 2 comment(s)
    Filed under:

    Comments

    Atlanta .NET Regular Guys said:


    Ok, so let's see where we are. In Post1 I talked about getting an animated gif on the screen to indicate...
    # January 13, 2006 10:59 AM

    Atlanta .NET Regular Guys said:


    Ok, so let's see where we are. In Post1 I talked about getting an animated gif on the screen to indicate...
    # January 13, 2006 11:00 AM