Solved

Threading - Using Invoke and Begin Invoke

Posted on 2007-04-05
16
589 Views
Last Modified: 2013-11-07
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
            ""
            ""
            ""
0
Comment
Question by:shahprabal
  • 9
  • 6
16 Comments
 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18859710
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
 
LVL 3

Assisted Solution

by:r1937
r1937 earned 100 total points
ID: 18859939
   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
 
LVL 14

Author Comment

by:shahprabal
ID: 18878905
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
 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18878914
If you're on a separate thread, you don't need DoEvents. Can you post your code?
0
 
LVL 14

Author Comment

by:shahprabal
ID: 18878960
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
 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18878980
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
 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18878987
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
 
LVL 14

Author Comment

by:shahprabal
ID: 18880275
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
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18882380
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
 
LVL 14

Author Comment

by:shahprabal
ID: 18882763
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
 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18882779
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
 
LVL 24

Accepted Solution

by:
Jeff Certain earned 400 total points
ID: 18882861
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
 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18882921
Another caveat: I'm not too sure about throwing an exception from BackgroundWorker, particularly raising a message box...
0
 
LVL 14

Author Comment

by:shahprabal
ID: 18883044
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
 
LVL 24

Expert Comment

by:Jeff Certain
ID: 18883117
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
 
LVL 14

Author Comment

by:shahprabal
ID: 18883267
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

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Wouldn’t it be nice if you could test whether an element is contained in an array by using a Contains method just like the one available on List objects? Wouldn’t it be good if you could write code like this? (CODE) In .NET 3.5, this is possible…
It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now