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 capacityDim 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