Threading - Using Invoke and Begin Invoke

I am fairly new to threading... This is what I do know...
- Other than the main thread all threads need to use Invoke/Begin Invoke to if they are accessing any controls on the UI
- Invoke.IsRequired can be used to check this...
- I can cheat using Control.CheckForIllegalCrossThreadCalls but I would want to get this working the "proper" way and understand what I am doing wrong here...

I cant get the below sub to work...  the commented code wont work cause the Sub populates a TreeView control on the form... I tried using delegates but havent used them so I am probably messing up somewhere...

    Private Sub GetConfigAndPopulateControls()
        RefreshValues()
        FillMyTreeView()
        'Dim StartThread As New System.Threading.Thread(AddressOf FillMyTreeView)
        'StartThread.Start()
    End Sub

    Private Delegate Sub FillMyTreeViewDelegate()
    Private Sub FillMyTreeView()

        Try
            If Me.InvokeRequired Then
                Me.BeginInvoke(AddressOf FillMyTreeView)
            End If
            Me.Cursor = Cursors.WaitCursor
            ""
            ""
            ""
LVL 14
shahprabalAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Jeff CertainCommented:
What I do is check if the invoke is required and invoke the delegate. The second time through, the invoke is not required, so do the work.

Here is the code I use to demonstrate how to do the invoke correctly; here I use BackgroundWorker in place of a thread (same concept though). Notice the AddItemCallback...

 Private WithEvents invokeWorker As New BackgroundWorker
  Private Sub btnBackgroundInvoke_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles btnBackgroundInvoke.Click
    invokeWorker.WorkerReportsProgress = True
    invokeWorker.RunWorkerAsync()
  End Sub

  Private Sub invokeWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) _
  Handles invokeWorker.DoWork
    ClearListBox(lbResults)
    timer.Reset()
    timer.Start()
    For i As Integer = 0 To maxCount
      AddItem(lbResults, i.ToString)
      invokeWorker.ReportProgress(i)
    Next
    timer.Stop()
    e.Result = timer.ElapsedMilliseconds
  End Sub

  Private Sub invokeWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
  Handles invokeWorker.ProgressChanged
    pbProgress.Value = e.ProgressPercentage
  End Sub

  Private Sub invokeWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
  Handles invokeWorker.RunWorkerCompleted
    txtTime.Text = e.Result.ToString
  End Sub

  Private Delegate Sub AddItemCallback(ByVal lb As ListBox, ByVal value As String)
  Private Sub AddItem(ByVal lb As ListBox, ByVal value As String)
    If lb.InvokeRequired Then
      Dim d As New AddItemCallback(AddressOf AddItem)
      Me.Invoke(d, New Object() {lb, value})
    Else
      lb.Items.Add(value)
    End If
  End Sub
0
r1937Commented:
   Private Sub GetConfigAndPopulateControls()
        RefreshValues()
        Dim StartThread As New System.Threading.Thread(AddressOf FillMyTreeView)
        StartThread.Start()
    End Sub

    Private Delegate Sub FillMyTreeViewDelegate()

    Private Sub FillMyTreeView()
        '=========This Sub should be used only to fill TreeView
        '=========If not, Use another sub that will call this sub and the StartThread should address that sub instead

        'If Me.InvokeRequired Then
        'Me.BeginInvoke(AddressOf FillMyTreeView)

        If Me.TreeView1.InvokeRequired Then
            Dim d As New FillMyTreeViewDelegate(AddressOf FillMyTreeView)
            Me.Invoke(d, New Object() {})
        Else
            'All code goes here
            Me.TreeView1.Nodes.Add("Node1")
        End If
        'End If

    End Sub
0
shahprabalAuthor Commented:
Thank you both...
I got the program to work both ways... i.e. using a background worker and without it...
First I tried without it... works as expected... but the UI is not responsive... when I put the DoEvents in the loop that populates the TreeView... the form UI responds better (not as good as I would have liked)...

So I looked at the background worker and implemented that solution thinking I can trigger the ProgressChanged event and have DoEvents inside the event... Also I thought since BackgroundWorker is a built in component it might work better with the UI... but triggering DoEvents through the event is not giving me the desired result...  

Also I tracked the CPU usage in both senarios... not even hitting 10%.... using Pentium D 3.00Ghz dual core cpu... any thoughts
0
Exploring SharePoint 2016

Explore SharePoint 2016, the web-based, collaborative platform that integrates with Microsoft Office to provide intranets, secure document management, and collaboration so you can develop your online and offline capabilities.

Jeff CertainCommented:
If you're on a separate thread, you don't need DoEvents. Can you post your code?
0
shahprabalAuthor Commented:
This Sub gets triggered in form load (could this be the issue) ??

~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Private Sub GetConfigAndPopulateControls()
        RefreshValues()
        bwrkr.RunWorkerAsync()
        'Dim StartThread As New System.Threading.Thread(AddressOf FillMyTreeView)
        'StartThread.IsBackground = True
        'StartThread.Start()
        Application.DoEvents()
        Me.Refresh()
    End Sub
~~~~~~~~~~~~~~~~~~~~~~~~~~~

I think I am beginning to see the difference between your sample Chaosian and my code... I think my code gets the invoke required and runs on the main thread... instead i could do everything other than populating the treeview in the background thread and just use the delegate to populate the treeview... is this correct? Can you please elaborate if so... I can't seem to wrap my head around this...

~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Private Delegate Sub FillMyTreeViewDelegate(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
    Private Sub FillMyTreeView(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bwrkr.DoWork

        Try
            If Me.InvokeRequired Then
                Dim d As New FillMyTreeViewDelegate(AddressOf FillMyTreeView)
                Me.Invoke(d, New Object() {sender, e})
            Else
                Try
                    'Me.Cursor = Cursors.WaitCursor
                    Dim dtSQLServers As DataTable = SmoApplication.EnumAvailableSqlServers(False)
                    Dim i, j As Integer
                    For Each drServer As DataRow In dtSQLServers.Rows
                        System.Threading.Thread.Sleep(100)
                        Application.DoEvents()
                        If boolStopLoading = True Then
                            Exit Try
                        End If
                        Dim ServerName As String
                        ServerName = drServer("Server").ToString
                        If Not (drServer("Instance") Is Nothing) AndAlso drServer("Instance").ToString.Length > 0 Then
                            ServerName += "\" + drServer("Instance").ToString
                        End If
                        TreeView1.Nodes.Add(New TreeNode(ServerName))
                        TreeView1.Nodes(i).ImageIndex = 1
                        TreeView1.Nodes(i).SelectedImageIndex = 1
                        Try
                            Dim SelectedServer As Server = New Server(ServerName)
                            SelectedServer.ConnectionContext.ConnectTimeout = 1
                            Dim DBCount As Int32 = 0
                            For Each db As Database In SelectedServer.Databases
                                'bwrkr.ReportProgress(1)
                                If Not db.IsSystemObject Then
                                    'System.Math.Min(System.Threading.Interlocked.Increment(DBCount), DBCount - 1)
                                    TreeView1.Nodes(i).Nodes.Add(New TreeNode(db.Name))
                                    TreeView1.Nodes(i).Nodes(j).ImageIndex = 0
                                    TreeView1.Nodes(i).Nodes(j).SelectedImageIndex = 0
                                    j += 1
                                End If
                                Application.DoEvents()
                            Next
                        Catch ex As Exception
                        End Try
                        i += 1
                    Next
                    Dim LocalServer As Server = New Server
                    Dim LocalServerName As String = LocalServer.Name
                    If Not (LocalServer.InstanceName Is Nothing) AndAlso LocalServer.InstanceName.Length > 0 Then
                        LocalServerName += "\" + LocalServer.InstanceName
                    End If
                    TreeView1.SelectedNode = TreeView1.Nodes.Item(LocalServerName)
                    ' Begin repainting the TreeView.
                    TreeView1.EndUpdate()
                    btnStopLoad_Click(Nothing, Nothing)
                Catch smoException As SmoException
                    MessageBox.Show(smoException.ToString)
                Catch exception As Exception
                    MessageBox.Show(exception.ToString)
                Finally
                    'Me.Cursor = Cursors.Default
                End Try
            End If
        Catch ex As Exception
        End Try

    End Sub
~~~~~~~~~~~~~~~~~~~~~~~~~~~
0
Jeff CertainCommented:
You got it! Move all of your data retrieval code to the background worker and let the work happen on that thread.

Also, get rid of the thread.sleep... that's just gonna slow things down.
0
Jeff CertainCommented:
I'll be AFK most of the rest of the day. However, if you still need more help on this one, I'll see about rearranging your code for you later tonight or early tomorrow.
0
shahprabalAuthor Commented:
Thanks Chaosian... I would like to see the right way this piece could be written... btw kudos on the MVP (how does one get one :) just curious)....
0
Jeff CertainCommented:
Well, the MVP award is for being involved in the community. I think you also need to be nominated by an existing MVP.

In my case, what they looked at for me were being a page editor here, speaking five or six times at various user groups in the last year (including at a code camp), and being involved in my local .NET user group.
0
shahprabalAuthor Commented:
cool indeed.... will you have time to post the fixed code... i m writting a version of my own and want to see if it close to yours...
0
Jeff CertainCommented:
Working on it now...

Among other things, I've removed your code to set the selected item after loading. This is, in part, because I didn't see it doing anything -- you create a new object then set the treeview seelcted item based on it's name. While I haven't used SMO, I expect this to give you back an empty string as the name.
0
Jeff CertainCommented:
Can't speak for the SMO stuff working. However, the threading should be pretty reasonable.

One minor change -- I made all your variable names (I think) start with a lower case letter. You currently have a mix.

Private Delegate Sub FillMyTreeViewDelegate(ByVal tv As TreeView, ByVal node As TreeNode)
      Private Sub AddTreeViewNode(ByVal tv As TreeView, ByVal node As TreeNode)
            Try
                  If Me.InvokeRequired Then
                        Dim d As New FillMyTreeViewDelegate(AddressOf AddTreeViewNode)
                        Me.Invoke(d, New Object() {tv, node})
                  Else
                        TreeView1.Nodes.Add(node)
                  End If
            Catch ex As Exception
            End Try
      End Sub

      Private Sub bkwkr_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bkwkr.DoWork
            Try
                  Dim dtSQLServers As DataTable = SmoApplication.EnumAvailableSqlServers(False)

                  For Each drServer As DataRow In dtSQLServers.Rows
                        If boolStopLoading = True Then Exit Sub

                        Dim serverName As String = drServer("Server").ToString
                        If Not (drServer("Instance") Is Nothing) AndAlso drServer("Instance").ToString.Length > 0 Then
                              serverName += "\" + drServer("Instance").ToString
                        End If

                        Dim tn As New TreeNode(ServerName, 1, 1)

                        Try
                              Dim selectedServer As New Server(serverName)
                              selectedServer.ConnectionContext.ConnectTimeout = 1

                              For Each db As Database In selectedServer.Databases
                                    If Not db.IsSystemObject Then tn.Add(New TreeNode(db.Name, 0, 0))
                              Next
                        Catch ex As Exception
                        End Try

                        ' since we've wrapped the treeview with an invoke wrapper to handle marshalling, we can call directly from here
                        AddTreeViewNode(TreeView1, tn)
                  Next
            Catch smoException As SmoException
                  MessageBox.Show(smoException.ToString)
            Catch exception As Exception
                  MessageBox.Show(exception.ToString)
            Finally
                  'Me.Cursor = Cursors.Default
            End Try
      End Sub

      Private Sub bkwkr_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bkwkr.RunWorkerCompleted
            btnStopLoad_Click(Nothing, Nothing)
      End Sub
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Jeff CertainCommented:
Another caveat: I'm not too sure about throwing an exception from BackgroundWorker, particularly raising a message box...
0
shahprabalAuthor Commented:
UI responds much better...

I am looking at this line :       If Not db.IsSystemObject Then tn.Nodes.Add(New TreeNode(db.Name, 0, 0))

smart way of adding child node... I was thinking I will need to add DoEvents in the Sub that adds the nodes since it will be called by the delegate and run on the main thread... and since its being called repeteadly... but it seems to be working fine without it...

Thanks again
0
Jeff CertainCommented:
Glad to help.

If you want better absolute performance, you can build a list of tree nodes in the BackgroundWorker and use AddRange in place of Add in the delegate. However, this looks slower, since the first UI update occurs when you're entirely done.

If you're expecting a lot of servers, you might consider a compromise -- use the technique above, but every N servers (5?) call the delegate.
0
shahprabalAuthor Commented:
Thats a good tip... Just some feedback regarding the msgbox... it seems to be working fine... popup without issues...  I moved the code to select the local server in the tree node to the RunWorkerCompleted event...  works nicely...
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.