VB.NET - Threading using Backgroundworker - Asked By ananda shankar on 02-Sep-11 05:39 AM

Hi all,

i am having a button and a datagrid in my screen. When i click the button i would like to update my Datagrid rows with some values. For this i am using a background worker. So i start the background worker on the button click event. And in that event i am calling a function which will update the grid..... In this process i am getting a UNHANDLED ERROR.. 

Please any one kind heart give me any suggestions or samples.... Thanks in advance

Anoop S replied to ananda shankar on 02-Sep-11 06:03 AM
Simple form example with a listbox, progress bar, & 2 buttons

Code:

Imports System.ComponentModel

Public Class Form1

    Private WithEvents bgw As BackgroundWorker = New BackgroundWorker

    Private Sub bgw_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgw.DoWork
      Dim ListText As String
      For Value As Integer = 0 To 100
        If bgw.CancellationPending Then
          Exit For
        End If
        ListText = String.Concat("Sequence #", Value)
        bgw.ReportProgress(Value, ListText)
        Threading.Thread.Sleep(100)
      Next
    End Sub

    Private Sub bgw_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgw.ProgressChanged
      ProgressBar1.Value = e.ProgressPercentage
      ListBox1.Items.Add(e.UserState)
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
      Button1.Enabled = False
      Button2.Enabled = True
      ListBox1.Items.Clear()
      ProgressBar1.Value = 0
      bgw.WorkerReportsProgress = True
      bgw.WorkerSupportsCancellation = True
      bgw.RunWorkerAsync()
      Me.Cursor = Cursors.WaitCursor
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
      bgw.CancelAsync()
    End Sub

    Private Sub bgw_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
      Button1.Enabled = True
      Button2.Enabled = False
      Me.Cursor = Cursors.Arrow
    End Sub
End Class

Since you're not going to know how long your query is going to take you won't be able to use ProgressChanged. Instead I'd suggest using a timer and stepping through the progress bar indefinitely until your worker finishes.

Code:

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
      If ProgressBar1.Value = ProgressBar1.Maximum Then
        ProgressBar1.Value = 0
      End If
      ProgressBar1.PerformStep()
    End Sub

To fill the datagridview fill your dataset in DoWork and then in RunWorkerCompleted you can assign the datasource.

Code:

DataGridView1.DataSource = ds.Tables(0)

refer
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
ananda shankar replied to Anoop S on 02-Sep-11 06:11 AM

This is my Code.... Please tell me in which code i have to modify to rectify that error...??

Private Function fnProcessTab(ByVal strTabKey As String, ByVal DtProgGridData As DataTable) As Boolean
'*************************************************************************
'* Function Name: ProcessTab
'* Description: This function validates the user input in the given
'* tab (based on tab key) and process it's activity
'* Created by: Anandashankar
'* Created date: 25/08/2011
'*************************************************************************

Dim blnResult As Boolean
Dim dtRowsAff As New DataTable

Select Case strTabKey
Case "SelectInputFile"
grdFilesUploadProgress.DataSource = DtProgGridData
grdFilesUploadProgress.DisplayLayout.Bands(0).Columns.Add("progKey", "")
grdFilesUploadProgress.DisplayLayout.Bands(0).Columns.Add("DescKey", "Status")
Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(3).CellActivation = Activation.Disabled
Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).CellActivation = Activation.Disabled
Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(1).CellActivation = Activation.Disabled
grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).EditorComponent = Me.upgPrgressBar
'Disable the next button
cmdNext.Enabled = False
'Set the active tab to the progress bar tab
utcWizard.Tabs("Progressbar").Selected = True
'Display hour glass cursor
Me.Cursor = Cursors.WaitCursor
bgWorker.RunWorkerAsync()
Application.DoEvents()
'Change the form caption based on the step we are at
'SetWizardCaption(3)
Me.Cursor = Cursors.Default

Case "SaveInput"

'Change the form caption based on the step we are at
SetWizardCaption(1)
'Enable the next button and return to the first tab
utcWizard.Tabs("SelectInputFile").Selected = True
cmdBack.Enabled = False
cmdNext.Enabled = True


End Select

End Function

Private Sub bgWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork

'*************************************************************************************
'* NAME: bgWorker_DoWork *
'* DESCRIPTION: This Sub is to load the grid and insert row by row from ultragrid *
'* WRITEN BY: Ananda shankar *
'* DATE: 25/08/2011 *
'*************************************************************************************
'Try
'grdFilesUploadProgress.DataSource = dtProgressData



fncheckrowbyrow()
If bgWorker.CancellationPending Then
e.Cancel = True
End If

'Catch ex As Exception
'MsgBox(ex.Message.ToString())
'End Try


End Sub
Private Sub fncheckrowbyrow()
'*************************************************************************************
'* NAME: fncheckrowbyrow *
'* DESCRIPTION: This main Sub is to import row by row *
'* WRITEN BY: Ananda shankar *
'* DATE: 25/08/2011 *
'*************************************************************************************
'Variable Declaration
Dim row As UltraGridRow
Dim dtfileinfo As New DataTable
Dim strFilename, strFileExtension As String
Dim objimportprodplan As New clsImportProductionPlan


For Each row In grdFilesUploadProgress.Rows
Try
intRowsAffcted = 0
dtfileinfo = New DataTable()
grdFilesUploadProgress.ActiveRow = Nothing
grdFilesUploadProgress.ActiveRow = grdFilesUploadProgress.Rows(row.Index)
' Me.UltraGrid1.Selected.Rows.AddRange(UltraGrid1.Rows.GetFilteredInNonGroupByRows())

strFilename = Me.grdFilesUploadProgress.Rows(row.Index).Cells(1).Value
strFileExtension = Path.GetExtension(strFilename)

If (strFileExtension = ".xls" Or strFileExtension = ".xlsx") Then
dtfileinfo = objCommon.ReadExcelFile(strFilename)
ElseIf strFileExtension = ".csv" Then
dtfileinfo = objCommon.ReadCSVFile(strFilename)
End If
Me.grdFilesUploadProgress.Rows(row.Index).Cells(3).Value = "Loading....."

'If (blnClosed = True) Then
' Exit For
'End If
'Threading.ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf fnImportByRow), dtfileinfo, row)
Dim blnImpSuccess As Boolean = fnImportByRow(dtfileinfo, row)
If blnImpSuccess = True Then
Me.grdFilesUploadProgress.Rows(row.Index).Cells(3).Value = intRowsAffcted.ToString & " Rows Completed"
Else
Me.grdFilesUploadProgress.Rows(row.Index).Cells(3).Value = intRowsAffcted.ToString & " Rows Completed. Error Loading file. Refer to Log File"
End If

Catch ex As Exception
fnImportByRow_Ex(row.Index, intRowsAffcted.ToString() & " Rows Completed.. Error Loading file. Refer to Log File")
End Try

Next

End Sub
Private Function fnImportByRow(ByVal dtinfotbl As DataTable, ByVal UGrow As UltraGridRow) As Boolean

'*************************************************************************************
'* NAME: fnImportByRow *
'* DESCRIPTION: This function is to import and for the Progress Bar in grid *
'* WRITEN BY: Ananda shankar *
'* DATE: 25/08/2011 *
'*************************************************************************************

Dim x As Double
Dim intProdId As Integer
Dim blnresult, blnSuccess As Boolean
clcmaxval_rows = New Collection

'System.Threading.Thread.Sleep(1000)
Try

If (Me.InvokeRequired) Then
'Me.Invoke(New MethodInvoker(AddressOf chk1, i))
Me.Invoke(New MethodInvoker(Function()


' Define a handler for unhandled exceptions for threads behind forms.
AddHandler Application.ThreadException, AddressOf MYThreadHandler

Try


Select Case Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(0).Value.ToString()

Case "BOM Parts List"



Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).MinValue = 0

'Code to Process BOM Parts List files

Dim ObjBOM As New clsImportMachineBOM(intOrganisationId)
ObjBOM.DeleteInputMachineBOMData(3)
clcmaxval_rows = ObjBOM.FnBulkCopyFromCSVToDB(Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(1).Value.ToString())

'Setting the maximum value for the Progressbar
If clcmaxval_rows.Count > 0 Then
Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).MaxValue = clcmaxval_rows(1)
intRowsAffcted = clcmaxval_rows(2)
End If

Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(2).Value = intRowsAffcted
Application.DoEvents()
' ObjBOM = Nothing


Case "Production Plan", "Install Base"

Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).MinValue = 0
Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).MaxValue = dtinfotbl.Rows.Count

For Each dtrow In dtinfotbl.Rows



intProdId = objCommon.GetProductId(dtrow(0), intOrganisationId, 1)

'Code to Process ProductionPlan type of files
If Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(0).Value.ToString() = "Production Plan" Then

Dim objProdPlanImport As New clsImportProductionPlan(intOrganisationId, CType(dtrow(1), Integer), CType(dtrow(2).ToString().Trim(), Integer), intProdId, CType(dtrow(3), Integer))
blnresult = objProdPlanImport.fnImportProductionPlan(2)
If blnresult = True Then
intRowsAffcted += 1
End If
Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(2).Value = intRowsAffcted
Application.DoEvents()
'objProdPlanImport = Nothing
'Code to Process Installbase type of files
ElseIf Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(0).Value.ToString() = "Install Base" Then

Dim ObjInstallBase As New ClsImportInstallBase(intOrganisationId, intProdId, CType(dtrow(1).ToString(), Integer), CType(dtrow(2).ToString.Trim(), Integer))
blnresult = ObjInstallBase.fnImportInstallBase(2)
If blnresult = True Then
intRowsAffcted += 1
End If
Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(2).Value = intRowsAffcted
Application.DoEvents()
'ObjInstallBase = Nothing
End If

If blnClosed = True Then
End
End If

Next

Case "Inventory"

Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).MinValue = 0

'Code to Process BOM Parts List files

Dim ObjInv As New clsImportInventory(intOrganisationId)
ObjInv.DeleteInventoryData(3)
clcmaxval_rows = ObjInv.FnBulkCopyFromCSVToDB(Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(1).Value.ToString())

'Setting the maximum value for the Progressbar
If clcmaxval_rows.Count > 0 Then
Me.grdFilesUploadProgress.DisplayLayout.Bands(0).Columns(2).MaxValue = clcmaxval_rows(1)
intRowsAffcted = clcmaxval_rows(2)
End If

Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(2).Value = intRowsAffcted
Application.DoEvents()

End Select


'Threading.Thread.Sleep(500)


Me.grdFilesUploadProgress.DisplayLayout.Rows(UGrow.Index).Cells(2).Style = ColumnStyle.CheckBox
Me.grdFilesUploadProgress.Rows(UGrow.Index).Cells(2).Value = True
blnSuccess = True

Return Nothing

Catch ex As Exception
blnSuccess = False
fnImportByRow_Ex(UGrow.Index, intRowsAffcted.ToString() & " Row Completed.. Error Loading file. Refer to Log File")
Return Nothing
End Try

End Function))
End If

Return blnSuccess
Catch ex As Exception
Throw ex
End Try




'UltraCheckEditor1.Checked = True

'SetControlPropertyValue(UltraControlContainerEditor1, "RenderingControl", ultra)

End Function
Private Sub fnImportByRow_Ex(ByVal ss As Integer, ByVal msg As String)

'*************************************************************************************
'* NAME: fnImportByRow_Ex *
'* DESCRIPTION: This function is called when any error occurs in the importing *
'* WRITEN BY: Ananda shankar *
'* DATE: 25/08/2011 *
'*************************************************************************************

'If (Me.InvokeRequired) Then
Me.Invoke(New MethodInvoker(Function()
Me.grdFilesUploadProgress.DisplayLayout.Rows(ss).Cells(3).Style = ColumnStyle.Edit
Me.grdFilesUploadProgress.Rows(ss).Cells(3).Value = msg
'SetControlPropertyValue(Ugcheckbox2, "Checked", False)
'Me.UgControlContainer.RenderingControl = Me.Ugcheckbox2
'Me.grdFilesUploadProgress.Rows(ss).Cells(2).EditorComponent = Me.UgControlContainer

Me.grdFilesUploadProgress.DisplayLayout.Rows(ss).Cells(2).Style = ColumnStyle.CheckBox
Me.grdFilesUploadProgress.Rows(ss).Cells(2).Value = False
Return Nothing
End Function))
'End If
End Sub


Public Sub MYThreadHandler(ByVal sender As Object, _
ByVal e As Threading.ThreadExceptionEventArgs)
MsgBox(e.Exception.StackTrace)

End Sub


Private Sub bgWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
'If e.Cancelled Then
' MessageBox.Show("The task has been cancelled")
'ElseIf e.[Error] IsNot Nothing Then
' MessageBox.Show("Error. Details: " & TryCast(e.[Error], Exception).ToString())

'End If
bgWorker.CancelAsync()
End Sub
pete rainbow replied to ananda shankar on 02-Sep-11 06:21 AM
would be helpful to have the whole exception message/stack

but in any case i suspect it's beacuse you are trying to access gui controls in the background worker thread which is strictly not allowed in windows.

this is what the invokerequired/invoke is for, that includes reading from controls well as updating

the normal pattern is to call the ReportProgress method passing in any data you need to pass over and handle it's corresponding ProgressChanged event in the gui thread

so if you are doing something like reading rows from a source ( eg file ) and want them processed then shown in a grid

read the file in the background worker and process in someway and then ReportProgress(rowNum, new BusinessObject());

where the BusinessObject would be the data after processing

then in thne gui thread handler you get this object and add to the datasource of the grid etc etc
ananda shankar replied to pete rainbow on 02-Sep-11 06:29 AM
pete rainbow replied to ananda shankar on 02-Sep-11 06:38 AM
yep that error is because you are accessing the data/gui in a non gui thread

see my previous post for the pattern to use

you must not access the gui in anyway from the workthread

just post the data as it is being processed row by row via the mechanism described and put into the grid in the handler

do not even try and create any gui rows in the background worker

worker threads are for data processing only
ananda shankar replied to pete rainbow on 02-Sep-11 06:42 AM
i have to process by reading cells from a particular column from the datagrid and i have to display the processing % in a progress bar with in the grid too.. So as per you said i have to loop through the datagrid rows and i have to start the background worker every time to do the process??
pete rainbow replied to ananda shankar on 02-Sep-11 06:59 AM
maybe post up what you are trying to do rather than the code...

what you may want to do is snap the data from the grid and pass into the worker thread at it's start

pete rainbow replied to ananda shankar on 02-Sep-11 07:08 AM
so lines like

strFilename = Me.grdFilesUploadProgress.Rows(row.Index).Cells(1).Value

need to be changed to get their data from either the backing data source if there is one, or create one and pass in

you should also create some kind of help  class that can be used to pass back out data and state events

so where you have code setting cells to 'Processing...'

maybe have a simple class, in c#, but you'll get the idea...

class Update
{
  enum UpdateType { PorcessingNotify, DataNotify , Random }
 
  int rowIndex;
  int colIndex;
  UpdateType updateType;
  object otherRandomData;
}

then in the process handler switch on the UpdateType and do whatever...

or you could define multiple classes and switch on the class type and the class type could have a method

virtual DoGuiUpdate(UltraGrid grid )
{
}

and each class would then do whatever it needed to to the grid
pete rainbow replied to ananda shankar on 02-Sep-11 07:11 AM
the other pattern you could use is when needing to update the gui is to call beginivokes on methods that do whatever you need and pass the data across, which will switch the code into the gui thread in an async way

but like i say golden rule don't access gui objects from other threads in any way at all

thats windows for you and has beenaround since win95 days and it's quite annoying that even with wpf et al it's still like that
ananda shankar replied to pete rainbow on 02-Sep-11 07:31 AM

Ok.. What i am trying to do is i am having a grid with 3 columns.. col 1 is displaying file path(c:\excl.xls)  second column a progressbar column and third one is a comment column

So i am now retrieving the data from the first row of first col (path) and filling the datatable. Later i am inserting the datas from this datatable in to database row by row and i have to show the number of inserting rows in the progressbar at the same time and once the progressbar reached its maximum value  the col 3 text will be changed to the "xxx rows inserted"

I think i have posted the concept here...... Please help me... Thanks for your time

pete rainbow replied to ananda shankar on 02-Sep-11 07:39 AM
so pass an array of the paths into the thread, that takes care of your reads

then change any places where you are modifying the grid to use the progress method and pass into the second argument a cookie type class to carry the rowindex, colindex, update text?

or the other way create a bunch of BeginInvokes that will switch into the gui thread for you, you could use anonymous delegates for that and the would be easy change to the code

i prefer the cookie class way i ddescribed above as it's more elegant...


ananda shankar replied to pete rainbow on 02-Sep-11 07:42 AM
Ok Thanks for your valuable comments... i will implement this now......and come back to a expert like you if i have some queries
pete rainbow replied to ananda shankar on 02-Sep-11 07:51 AM
an example ... not sure if vb has anonymous delegate so do old fashioned way
   
changing this...
   
grdFilesUploadProgress.ActiveRow = Nothing
grdFilesUploadProgress.ActiveRow = grdFilesUploadProgress.Rows(row.Index)
   
to this...
   
grdFilesUploadProgress.BeginInvoke
   
Dim myArray(1) As Object
   myArray(0) = arrayRowIndex
   grdFilesUploadProgress.BeginInvoke(New MyDelegate(AddressOf ChangeActiveRow), myArray)
   
Public Sub ChangeActiveRow(rowIndex As Int)
   grdFilesUploadProgress.ActiveRow = Nothing
   grdFilesUploadProgress.ActiveRow = grdFilesUploadProgress.Rows(rowIndex)
End Sub 'DelegateMethod
 
although using the array index and the row.rowIndex could get out of sync if the grid is sorted, so maybe you might want to use a dictionary/key lookup in some way
pete rainbow replied to ananda shankar on 02-Sep-11 07:53 AM
no problems :-)

the other way to implement this is to have a bound data source that is a list of objects with the path,progress etc as properties and bind the grid to that using BindingSource<>

then in the worker thread just update those objects in a thread safe way, the grid should then show data changes as you'd expect

you could even use a DataTable i guess...
ananda shankar replied to pete rainbow on 06-Sep-11 12:52 AM

Hi,

I used as per u said and now i didnot received such unhandled exception error.... Thanks alot for helping me in solving this......... and now my question is "Is it Possible to work with more than one rows at the same time within a grid??"

pete rainbow replied to ananda shankar on 06-Sep-11 02:18 AM
not sure what you mean by that question?

but if you mean can you have more than one background worker thread running

then yes you can and as long as you marshal the updates into the gui thread as you are now doing then all should work ok


ananda shankar replied to pete rainbow on 06-Sep-11 02:22 AM

FIrst i am taking the first cell from col1 to read all the excel datas from the path... and i am inserting the records to database.. Thn i am taking next row and do the same...... like wise i worked for row by row on datagrid........

but what i am askin is at the same time shall i read values from the three rows and inserting respective records to the database table??

I think i have explained the question very well................

pete rainbow replied to ananda shankar on 06-Sep-11 03:22 AM
yes you should be able to do that fine

note you might get contention when doing the insert into db if more than one thread doing at the same time

something you'll have to play about with

a general pattern for this sort of thing is to use worker queues and throw tasks onto them and then have worker threads that pull tasks off and do them

that way you can play about with the number of threads etc

maybe also then split the reading data task from the inserting into db task

the task classes would contain all the info that they needed to do the task, eg path, or array of data read

and a virtual Action method