Link to home
Start Free TrialLog in
Avatar of biplavo
biplavoFlag for United States of America

asked on

Cross-thread problem with Delegate

I am adding controls in a flowlayout panel at runtime ( calling sbGuiLoader as shown in the code below) but when i call the procedure sbGuiLoader() directly; it takes too much time to add the controls dynamically.
So i have tried to call it on a separate thread  by utlizing Delegate so that it wont block the UI but i got an error which says:

"Cross-thread operation not valid: Control 'SzAdderPnl' accessed from a thread other than the thread it was created on".Since I amnot that experienced with Delegates; could you guys help me out please?

---------------Code goes like this-------------------

 Public Delegate Sub delLdItm()

 Private Sub tbItm_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tbItm.SelectedIndexChanged
       
      If tbItm.SelectedIndex =1 Then
       
            Dim objDelLdItm As New delLdItm(AddressOf  sbGuiLoader)
            objDelLdItm.BeginInvoke(Nothing, Nothing)
                End If
    End Sub

 Private Sub sbGuiLoader()
        Dim cnOrd As OleDbConnection = clsDb.retCn
        Dim drSz As OleDbDataReader = Nothing

        Try
            cnOrd.Close()
            cnOrd.Open()
            Dim cmd As New OleDbCommand("Select SizeId,ItemSize from tbl_SizeTDef", cnOrd)
            drSz = cmd.ExecuteReader
            Me.SzAdderPnl.Controls.Clear()
            While drSz.Read
                Dim chkSz As New CheckBox
                chkSz.Name = drSz.GetString(0)
                chkSz.Text = drSz.GetString(1)
                cmbSz.Items.Add(drSz.GetString(1))
                SzAdderPnl.Controls.Add(chkSz)
            End While
         Catch ex As Exception
            MsgBox(ex.ToString)
        Finally
            drSz.Close()
            cnOrd.Close()
        End Try
    End Sub


 
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Try something like...

    Private T As System.Threading.Thread = Nothing
    Private Delegate Sub AddControlDelegate(ByVal ctl As Control)
    Private Delegate Sub DoneDelegate()

    Private Sub tbItm_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tbItm.SelectedIndexChanged
        If Me.tbItm.SelectedIndex = 1 Then
            If T Is Nothing Then
                Me.tbItm.Enabled = False
                Me.SzAdderPnl.Controls.Clear()
                T = New System.Threading.Thread(AddressOf sbGuiLoader)
                T.Start()
            End If
        End If
    End Sub

    Private Sub sbGuiLoader()
        Dim cnOrd As OleDbConnection = clsDb.retCn
        Dim drSz As OleDbDataReader = Nothing

        Try
            cnOrd.Close()
            cnOrd.Open()
            Dim cmd As New OleDbCommand("Select SizeId,ItemSize from tbl_SizeTDef", cnOrd)
            drSz = cmd.ExecuteReader
            While drSz.Read
                Dim chkSz As New CheckBox
                chkSz.Name = drSz.GetString(0)
                chkSz.Text = drSz.GetString(1)
                cmbSz.Items.Add(drSz.GetString(1))
                AddControl(chkSz)
            End While
        Catch ex As Exception
            MsgBox(ex.ToString)
        Finally
            drSz.Close()
            cnOrd.Close()
            Done()
        End Try
    End Sub

    Private Sub AddControl(ByVal ctl As Control)
        If Me.InvokeRequired Then
            Me.Invoke(New AddControlDelegate(AddressOf AddControl), New Object() {ctl})
        Else
            Me.SzAdderPnl.Controls.Add(ctl)
        End If
    End Sub

    Private Sub Done()
        If Me.InvokeRequired Then
            Me.Invoke(New DoneDelegate(AddressOf Done))
        Else
            tbItm.Enabled = True
            T = Nothing
        End If
    End Sub
Avatar of biplavo

ASKER

Sir,
What does the following lines of code mean or do:


  If Me.InvokeRequired Then
            Me.Invoke(New AddControlDelegate(AddressOf AddControl), New Object() {ctl})

End If
The AddControl() sub is called from the thread we created right?  So the calling sub is "T":

    Private Sub AddControl(ByVal ctl As Control)
        If Me.InvokeRequired Then
            Me.Invoke(New AddControlDelegate(AddressOf AddControl), New Object() {ctl})
        Else
            Me.SzAdderPnl.Controls.Add(ctl)
        End If
    End Sub

The first line:

    If Me.InvokeRequired Then

Determines if "Me" was created on the same thread as the caller.  "Me" refers to the Form which was created by the main UI thread.  Since "T" is not the main UI thread, InvokeRequired() returns True.

The next line is then executed:

        Me.Invoke(New AddControlDelegate(AddressOf AddControl), New Object() {ctl})

The Invoke() method takes a Delegate and runs that Delegate on the thread that created the caller.  The caller in this case is "Me" (the main UI thread) because we used:

        Me.Invoke(...)

A Delegate is simply a "pointer to a function".  Here we create a pointer to the AddControl() sub:

                       New AddControlDelegate(AddressOf AddControl)

This is the sub that we are already in thus making this a RECURSIVE call.  So the Invoke() method is going to make a recursive call to AddControl() and we pass the parameters from the first call to the second call by creating an array of object:

                                                                                              New Object() {ctl}

and passing that to Invoke() as well.

This is KEY...the first time AddControl() is called it is from thread "T", but the second time it is called (via Me.Invoke) it is called from the main UI thread.

So we enter AddControl() AGAIN (from the recursive call) and hit the first line once more:

    If Me.InvokeRequired Then

But this time InvokeRequired() returns False because "Me" was infact created on the same thread as the caller.  The caller this time is the main UI thread.

Since we are on the same thread as the main UI it is now SAFE to modify or add controls to the GUI.

Now the second half of the If...Then statement is executed:

            Me.SzAdderPnl.Controls.Add(ctl)

And hopefully at this point we have gotten rid of your "Cross-thread operation not valid" error.

Note that recursion is NOT a requirement here.  That is simply the way I chose to do it.  We could have created a "helper" sub to receive the control to add and made the Delegate point to that instead.

Does that help at all?  It seems long winded and confusing when I write it all down but when I look at the code now it just looks "right" to me.

Yell at me if you need more or a different explanation...  =)
Avatar of biplavo

ASKER

Dear Sir,

Now the problems occurs here:
cmbSz.Items.Add(drSz.GetString(1))

When I change the tab index and came back again....
it produces an error message which says....

Cross-thread operation not valid: Control 'CmbSz' accessed from a thread other than the thread it was created on.

Can you show us the code you are using now?...
Avatar of biplavo

ASKER

Here goes the total code supported by your code.......

Private T As System.Threading.Thread = Nothing
    Private Delegate Sub AddControlDelegate(ByVal ctl As Control)
    Private Delegate Sub DoneDelegate()

 Private Sub sbGuiLoader()
        Dim cnOrd As OleDbConnection = clsDb.retCn
        Dim drSz As OleDbDataReader = Nothing

        Try
            cnOrd.Close()
            cnOrd.Open()
            Dim cmd As New OleDbCommand("Select SizeId,ItemSize from tbl_SizeTDef ", cnOrd)
            '-----------Load Defined Size in "Add New Food Type"---------'
            drSz = cmd.ExecuteReader
            Me.SzAdderPnl.Controls.Clear()
            While drSz.Read
                Dim chkSz As New CheckBox
                chkSz.Name = drSz.GetString(0)
                chkSz.Text = drSz.GetString(1)
                cmbSz.Items.Add(drSz.GetString(1))
                AddControl(chkSz)

                'SzAdderPnl.Controls.Add(chkSz)
            End While
            '-------------End Load---------------------------------------'
        Catch ex As Exception
            MsgBox(ex.ToString)
        Finally
            drSz.Close()
            cnOrd.Close()
            Done()

        End Try
    End Sub

 Private Sub AddControl(ByVal ctl As Control)
        If Me.InvokeRequired Then
            Me.Invoke(New AddControlDelegate(AddressOf AddControl), New Object() {ctl})
        Else
            Me.SzAdderPnl.Controls.Add(ctl)
        End If
    End Sub

    Private Sub Done()
        If Me.InvokeRequired Then
            Me.Invoke(New DoneDelegate(AddressOf Done))
        Else
            tbItm.Enabled = True
            T = Nothing
        End If
    End Sub


 
    Private Sub tbItm_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tbItm.SelectedIndexChanged
        If tbItm.SelectedIndex = 0 Then
            tbItm.Size = New Size(454, 399)
            If T Is Nothing Then
                Me.tbItm.Enabled = False
                Me.SzAdderPnl.Controls.Clear()
                T = New System.Threading.Thread(AddressOf sbGuiLoader)
                T.Start()
            End If
        ElseIf tbItm.SelectedIndex = 1 Then
.....................

       ElseIf tbItm.SelectedIndex = 2 Then
.........................      

        ElseIf tbItm.SelectedIndex = 3 Then
      ......................

        End If
    End Sub
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of biplavo

ASKER

Thank You for spending time for my question.
I find this 'Area' pretty interesting; i  want to explore more on this topic; so beside this method what should I "Google" for to do it on a different way.
Just google for:

    vb.net thread marshal delegate invoke

or:
   
    vb.net multithreading gui