Thread Safe Calls

JeffvClayton
JeffvClayton used Ask the Experts™
on
I have a progress bar set to marquee on an mdi parent status bar. To make it run smoothly and continuously while updating I thought it may be time to try somehting new rather than Application.DoEvents.

So I have a procedure in a child form which is called by the following

           ....enable the progress bar

            Dim newthread As New Threading.Thread(AddressOf MyProcedure)
            newthread.Start()

         
The procedure fills a datatable from various tables, then a DataView which is sorted, which is then used to populate a DataGridView control. I have found out that (via debug message) I have to check the DataGridView.InvokeRequired property when i try to set the DataGridView datasource, and then do something else if the answer is True, when doing something to a control not invoked by the same thread. I am not sure what code I need if the answer is true.

so..

    Private Sub MyProcedure()


        ....do some stuff

         If MyDataGridView.InvokeRequired Then

            ......WHAT GOES HERE

          else

             MyDataGridView.DataSource = MyDataView

        End If

        'Disable progress bar
        me.cursor = cursors.default
        MyMdiParent.ProgressBar.Enabled=false
       

PS. I have also discovered i will get the same problem when i try to reset the cursor for the form back to default and also try to disable the progress bar as they were set on different threads

End Sub

I am assuming of course this is the way to go for smoothly operating progress bars
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Interesting as I was tinkering with a progress bar in a separate thread myself earlier today.
However, I'm not sure what exactly you are looking for. Do you want the progress bar in the background, marquee-ing smoothly? Then that should be in the separate thread.

Something like this:
Dim newthread As New Threading.Thread(AddressOf MyProcedure)
            newthread.Start()

   ' Do whatever you have to do
  '
   newthread.abort()     ' kill it


Private sub MyProcedure()
            Dim p As New ProgressBar
            p.Maximum = 10
            p.Minimum = 1
            Dim waitform As New Form
            waitform.Text = "Doing Something... Wait"
            waitform.MinimizeBox = False
            waitform.MaximizeBox = False
            waitform.Controls.Add(p)
            p.Left = (waitform.Width - p.Width) / 2
            p.Top = (waitform.Height - p.Height) / 2
            p.Visible = True
            waitform.Show()
            While True
                Thread.Sleep(250)   ' will update the progress bar every 250 milli seconds
                If p.Value = p.Maximum Then
                    p.Value = p.Minimum
                Else
                    p.Value += 1
                End If
            End While

        End Sub


Author

Commented:
Yes i do you want the progress bar in the background, marquee-ing smoothly, but I do not want to create a new form, i have a progress bar in a status strip at the bottom of an Mdi parent which is in the original thread, I want to use that. Does it mean i basically have to add the progress bar to the status strip at runtime? Or can i manage the invokerequired properties as suggested by microsoft?
I'm not sure about the "invokerequired" property. You can try creating a progress bar at runtime and accessing it from the thread.
JavaScript Best Practices

Save hours in development time and avoid common mistakes by learning the best practices to use for JavaScript.

Author

Commented:
OK, that is something to try.

I have also tried this as the last line in the procedure with no error messages, but no data in the DataGridView either!

 MyDataGridView.Invoke(MyDataGridView.DataSource, MyDataView.ToTable)
Sorry, in the earlier post I meant create a progress bar at design time, not runtime
You need to supply a delegate sub, kind of like a template that your code passes through. You then invoke through that delegate. If you have parameters that you need to pass, the delegate sub contains those same params. The delegate sub is only one line...

----------------------------------------------
Delegate Sub InvokeToControl(AnyParam1 as Type, AnyParam2 as Type)
----------------------------------------------

Then specify the sub routine that holds your code...

----------------------------------------------
Sub YourSub(AnyParam1 as Type, AnyParam2 as Type)
'Do whatever needs doing
End sub
----------------------------------------------

Now...

----------------------------------------------
If MyDataGridView.InvokeRequired Then
MyDataGridView.Invoke(New InvokeToControl(AddressOf YourSub), New Object() {AnyParam1, AnyParam2})
Else
YourSub(AnyParam1, AnyParam2)
----------------------------------------------

In this example i have specified 2 paramaters, obviously adjust as you need to, but if you have none, then the invoke line just becomes:

MyDataGridView.Invoke(New InvokeToControl(AddressOf YourSub))

Hope this helps

Dez

Author

Commented:
I have solved the problem, you cant change an object from a different thread, you need all this....


Instead of   'MyDataGridView.DataSource = MyDataView' as the last line of the procedure in the new thread

                              you need a new procedure call as the last line e.g.   PassNewDataView(MyDataView.ToTable)

                                                                                                         End Sub
                               'Another sub which runs in the new thread
                             '--------------------------------------------------
                                Sub PassNewDataView(ByVal MyDataView As DataTable)
                                          Me.BeginInvoke(New DelegateUpdateSource(AddressOf Me.ChangeSource), New Object() {MyDataView})
                               End Sub

                              ' then a new Delegate Sub as this....
                               ' ----------------------------------------
                               Public Delegate Sub DelegateUpdateSource(ByVal DV As DataTable)


  'Finally a sub which runs in the original thread to change the datasource and stop the progress bar                                  
   '-----------------------------------------------------------------------------------------------------------------

    Sub ChangeSource(ByVal DV As DataTable)

        Me.MyDataGridView.DataSource = DV

        Me.Cursor = Cursors.Default
         MParent.PB1.Enabled = False
     

    End Sub

Thanks anyway

Jeff

Author

Commented:
Thanks Dez, I was typing as you sent the message.
I would of typed it a hell of a lot sooner (just as you posted the msg) but i was on my phone the whole time! Sorry mate!

Glad its working now anyway.

Cheers,

Dez
one more thing...

                               Sub PassNewDataView(ByVal MyDataView As DataTable)
                                          Me.BeginInvoke(New DelegateUpdateSource(AddressOf Me.ChangeSource), New Object() {MyDataView})
                               End Sub

There is no need for a seperate sub here... you can just call:

Me.BeginInvoke(New DelegateUpdateSource(AddressOf Me.ChangeSource), New Object() {MyDataView})

... from anywhere on the thread

Cheers,
Dez

Author

Commented:
Even Better!

Thanks Dez.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial