Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
?
Solved

Threading - Using Invoke and Begin Invoke

Posted on 2007-04-05
16
Medium Priority
?
609 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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 400 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
Is Your Team Achieving Their Full Potential?

74% of employees feel they are not achieving their full potential. With Linux Academy, not only will you strengthen your team's core competencies but also their knowledge of of the newest IT topics.

With new material every week, we'll make sure that you stay ahead of the game.

 
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
 
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 1600 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

RHCE - Red Hat OpenStack Prep Course

This course will provide in-depth training so that students who currently hold the EX200 & EX210 certifications can sit for the EX310 exam. Students will learn how to deploy & manage a full Red Hat environment with Ceph block storage, & integrate Ceph into other OpenStack service

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article describes relatively difficult and non-obvious issues that are likely to arise when creating COM class in Visual Studio and deploying it by professional MSI-authoring tools. It is assumed that the reader is already familiar with the cla…
Many of us here at EE write code. Many of us write exceptional code; just as many of us write exception-prone code. As we all should know, exceptions are a mechanism for handling errors which are typically out of our control. From database errors, t…
In this video you will find out how to export Office 365 mailboxes using the built in eDiscovery tool. Bear in mind that although this method might be useful in some cases, using PST files as Office 365 backup is troublesome in a long run (more on t…
In this video, Percona Solution Engineer Rick Golba discuss how (and why) you implement high availability in a database environment. To discuss how Percona Consulting can help with your design and architecture needs for your database and infrastr…

671 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