Background worker problem

MichaelB

New member
Joined
Sep 19, 2006
Messages
2
Location
Tucson, AZ
Programming Experience
10+
Hi,

I've recently switched from PowerBuilder to VB.Net 2005 with Visual Studio and am maintaining an existing program. I have a problem with background worker threads calling a form method on the UI thread.

The short form of my question is: When a background worker defined and called from a form calls a business layer method that needs to call a method in the form on the main UI thread, how can that call be performed?

The details of my question are:
I have a DataGridView on a form that needs to keep track of rows highlighted by the application when other rows are inserted or deleted. The form currently keeps an ArrayList of highlighted rows and calls background workers in the form to handle inserts and deletes. A threadsafe delegate allows the background workers to call a method to maintain the ArrayList of highlighted rows and this works with no problems for inserts and simple deletes.

Some rows in the DataGridView are slaves to a master row and, when a master row is deleted, methods in the business layer of the application deleted the slaves related to that master. I thought I could define and raise an event in the business layer that is handled by a method in the form. However, when the event is raised, the code in the handler is not run.

My method declarations are:
VB.NET:
'--The background worker for deletions is in the form.
Private Sub _deleteWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles _deleteWorker.DoWork
 
'--The DoWork script calls a business layer method in the bound data source for the DataGridView to perform deletions.
_project.Resources.DeleteResource(resource, counter, False)
 
'--It then calls a method in the form to maintain the ArrayList of highlighted rows.
Me.MaintainRowHighlightRecords("Delete", deletedIndex, Nothing)
 
'--That method is defined with a delegate.
Public Delegate Sub MaintainRowHighlightRecordsCallBack(ByVal action As String, ByVal rowIndex As Integer, ByVal rowColor As Color)
 
'--The method itself determines if the delegate is called.
Public Sub MaintainRowHighlightRecords(ByVal action As String, ByVal rowIndex As Integer, ByVal rowColor As Color)
Dim currentCursor As System.Windows.Forms.Cursor = System.Windows.Forms.Cursor.Current
Try
If Me.InvokeRequired Then
Dim del As New MaintainRowHighlightRecordsCallBack(AddressOf MaintainRowHighlightRecords)
Dim args() As Object = {action, rowIndex, rowColor}
Me.Invoke(del, args)
Else
<the method code>
 
'--Up to this point, everything works fine. The problem occurs when slave rows are deleted from a method in the business layer. 
The deletion process appears more complex than necessary because of additional code that provides full Undo/Redo capability. 
The relevant calls are in red.
 
Public Sub DeleteResource(ByRef resourceToDelete As Resource, ByVal actionCounter As Integer, ByVal notify As Boolean)
Try
If resourceToDelete.IsCerGeneratedBase Then
'--Delete slave rows.
[COLOR=red]_project.Resources.DeleteCreatedCers(resourceToDelete, actionCounter, notify)[/COLOR]
End If
Dim itemIndex As Integer = Me.IndexOf(resourceToDelete)
Dim factory As New Data.ResourceFactory
factory.Delete(resourceToDelete, actionCounter, Nothing)
Me.Items.RemoveAt(itemIndex)
Catch ex As Exception
Throw
End Try
End Sub
 
Public Sub DeleteCreatedCers(ByRef resource As Resource, ByVal actionCounter As Integer, ByVal Notify As Boolean)
Try
'--Remove the old Cer generated rows
Dim cers As ArrayList = Resources.CerGroup.GetCerGeneratedRowsFromBaseResourceRecord(_project.Proposal.TiesControlNumber, resource.SerialNumber)
Dim i As Integer = 0
'--Delete the data from the database, then remove it from the
'--DataGridView data source.
For Each unit As Integer In cers
For i = (Me.Items.Count - 1) To 0 Step -1
If Items(i).SerialNumber = CInt(unit) Then
Dim factory As New Data.ResourceFactory
factory.Delete(Items(i), actionCounter, Nothing)
[COLOR=red]RemoveCerGeneratedRecords(i)[/COLOR]
Exit For
End If
Next
Next
Catch ex As Exception
Throw
End Try
End Sub
 
Public Sub RemoveCerGeneratedRecords(ByVal deletedIndex As Integer)
Try
Me.Items.RemoveAt(deletedIndex)
 
'--Record the deleted index and raise an event handled in 
'--the form to maintain highlighted and selected row indices.
[COLOR=red]Me._deletedCERIndex = deletedIndex[/COLOR]
[COLOR=red]RaiseEvent CERGeneratedRowDeleted(Me, EventArgs.Empty)[/COLOR]
Catch ex As Exception
Throw
End Try
End Sub
 
'--The CERGeneratedRowDeleted event is defined in the business layer as:
Public Event CERGeneratedRowDeleted As EventHandler
 
'--Back in the form, a method is defined to handle the event but the code never runs.
Public Delegate Sub GeneratedCERRowDeletedHandlerCallBack(ByVal sender As Object, ByVal e As System.EventArgs)
 
Public Sub GeneratedCERRowDeletedHandler(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles _resourceList.CERGeneratedRowDeleted
Dim currentCursor As System.Windows.Forms.Cursor = System.Windows.Forms.Cursor.Current
Try
If Me.InvokeRequired Then
Dim del As New GeneratedCERRowDeletedHandlerCallBack(AddressOf GeneratedCERRowDeletedHandler)
Dim args() As Object = {sender, e}
Me.Invoke(del, args)
Else
'--ResourceList.vb recorded the index of the deleted CER 
'--generated row. Call the sub to maintain the correct indices
'--for highlighted rows
If _resourceList._deletedCERIndex >= 0 Then
Me.MaintainRowHighlightRecords("CER", _resourceList._deletedCERIndex, Nothing)
End If
End If
Catch ex As Exception
Throw
Finally
System.Windows.Forms.Cursor.Current = currentCursor
End Try
End Sub
 
Last edited by a moderator:
If you need to do something on the UI from a BackgroundWorker you can call the ReportProgress method to pass data to the ProgressChanged event handler, which is executed on the UI thread. You can also use standard delegation as you would without a BackgroundWorker. See here for an example.
 
The existing code uses standard delegation with no problems when the call to a method in the form comes from another method in the form that is running on the background worker thread. The problem occurs when a call to a method in the form needs to be made from a method in the business layer running on the background worker thread. In this case, the business layer method has no reference to the presentation layer form to make the call directly. My solution was to declare and raise an event in the business layer object and declare a handler method (with delegate) in the form (see the code snippets in my original post). Everything compiles and runs but, when the event is raised, the handler code in the form does not execute (i.e., a debug stop placed on the handler method declaration or first instruction is never hit).
 
Back
Top