Background Worker Illegal crossthread operation

A user of the MSDN forums asked the following question:(in this thread)

Hi, I want to thank everyone in advance for any help you can offer. I am an amateur programmer and I have been able to turn to this forum repeatedly for help with my problems — and this is deeply appreciated by me! Thank you!

I want to load a .csv file into a datagridview. The .csv file has 10 columns and at least 70,000 to 100,000 rows. When the program loads the file, the whole program freezes for several minutes. I eventually figured out that I could use a background worker to do things. However, when I tried to have the background worker add rows to the datagridview, I got an error message saying that it couldn’t because the datagridview was created on another thread than its own. …. I am wondering how I can solve this issue? Here is the program:

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        BackgroundWorker1.RunWorkerAsync()
    End Sub
    Private Sub bw() Handles BackgroundWorker1.DoWork
        get_main_catalog_of_items()
    End Sub
    Private Sub get_main_catalog_of_items()
        Datagridview1.Rows.Clear()
        Using MyReader As New Microsoft.VisualBasic.FileIO.TextFieldParser("c:\catalogs\catalog_main.csv")
            MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
            MyReader.Delimiters = New String() {vbTab}
            Dim currentRow As Strng()
            While Not MyReader.EndOfData
                Try
                    currentRow = MyReader.ReadFields()
                    DataGridView1.Rows.Add(currentRow)
                Catch ex As Exception
                End Try
            End While
        End Using
    End Sub
End Class

Basically, what is happening is that when you access an object on one thread from another thread, this creates an unstable system environment. The way for getting around this is to check and see if an invoke is required for that object, and basically, if it is, you invoke a new delegate sub on the same thread of the datagridview, hence avoiding access across threads, because the delegate sub exists on the same thread as the datagridview. See example

Example

Option Strict On
Option Explicit On
Option Infer Off
Public Class Form1
    Dim bgw As New System.ComponentModel.BackgroundWorker
    Private Delegate Sub addDGVRow(dgv As DataGridView, row As DataGridViewRow)
    Sub doRowAdd(dgv As DataGridView, row As DataGridViewRow)
        If dgv.InvokeRequired Then
            dgv.Invoke(New addDGVRow(AddressOf doRowAdd), dgv, row)
        Else
            dgv.Rows.Add(row)
        End If
    End Sub
    Sub work(sender As Object, e As EventArgs)
        For i As Integer = 0 To 1000
            Dim row As New DataGridViewRow
            row.Cells.Add(New DataGridViewTextBoxCell With {.Value = i.ToString})
            row.Cells.Add(New DataGridViewTextBoxCell With {.Value = (i + 1).ToString})
            doRowAdd(DataGridView1, row)
        Next
    End Sub
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        DataGridView1.ColumnCount = 2
        AddHandler bgw.DoWork, AddressOf work
        bgw.RunWorkerAsync()
    End Sub
End Class

Here is the solution applied to your code:

Option Strict On
Option Explicit On
Option Infer Off
Imports System.ComponentModel
Public Class Form1
    Private Delegate Sub addDGVRow(dgv As DataGridView, row As String())
    Sub doRowAdd(dgv As DataGridView, row As String())
        If dgv.InvokeRequired Then
            dgv.Invoke(New addDGVRow(AddressOf doRowAdd), dgv, row)
        Else
            dgv.Rows.Add(row)
        End If
    End Sub
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        BackgroundWorker1.RunWorkerAsync()
    End Sub
    Private Sub bw() Handles BackgroundWorker1.DoWork
        get_main_catalog_of_items()
    End Sub
    Private Sub get_main_catalog_of_items()
        Datagridview1.Rows.Clear()
        Using MyReader As New Microsoft.VisualBasic.FileIO.TextFieldParser("c:\catalogs\catalog_main.csv")
            MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
            MyReader.Delimiters = New String() {vbTab}
            Dim currentRow As String()
            While Not MyReader.EndOfData
                currentRow = MyReader.ReadFields()
                doRowAdd(DataGridView1, currentRow)
            End While
        End Using
    End Sub
End Class
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s