[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 433
  • Last Modified:

Cross thread operations with 2.0 and above

I have a form that is simply checking the time.  Every hour I would like it to perform some operations that are normal subs in the code.  I am used to the older version and never had a problem with calling a control contained somewhere in the form to update it.  Now I am getting these cross thread errors and can't go any further.  Can someone please explain how to this lame-headed idiot?

It is pretty simple, I'm running the timer with this:

        Public Sub StartCurrentTimeTimer()
            If IsNothing(mytimer) Then
                mytimer = New System.Timers.Timer(1000)
                AddHandler mytimer.Elapsed, AddressOf OnCurrentTimeTimer
                mytimer.Interval = 1000
                mytimer.Enabled = True
            End If
        End Sub

        Private Sub StopCurrentTimeTimer()
            mytimer.Enabled = False
            mytimer = Nothing
        End Sub

        Private Sub SetText(ByVal [text] As String)
            Try
                If Me.lblCurrentTime.InvokeRequired Then
                    Dim d As New CurrentTimerCallBack(AddressOf SetText)
                    Me.Invoke(d, New Object() {[text]})
                Else
                    Me.lblCurrentTime.Text = [text]
                End If
            Catch ex As Exception

            End Try
        End Sub

I'm trying to do something in the OnCurrentTimeTimer routine.  I've read all about this but just can't seem to get my head around it.  HELP!!
0
ddepuemd
Asked:
ddepuemd
  • 4
  • 3
1 Solution
 
Jeff CertainCommented:
The issue is that Microsoft finally decided to "help" you out... there were potential race conditions when updating the UI from multiple threads.

There's two solutions here -- the easy one and the right one.

The easy one is to set CheckForIllegalCrossThreadCalls = False.

The right solution is to do cross-thread marshalling. If you're using System.ComponentModel.BackgroundWorker (also new in 2.0), this is pretty much free. Otherwise, you can do it manually. The pattern looks like this:

    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

Be aware that Invoke has a performance hit associated with it -- it's not a big deal if you're only hitting it once, but if you were to do something like updating a UI element (say a listbox) a few hundred or a few thousand times a second, you may want to reconsider your approach.
0
 
ddepuemdAuthor Commented:
How would you do it for a standard sub that updates a datagridview.  Here is what I am doing:

        Private Sub SaveAndShiftTimeGraph(ByVal newhour As Integer)
            Dim frm As New StatusUpdate
            frm.lblStatus.Text = "Updating data, please wait..."
            frm.BackColor = sbgcolor
            frm.ForeColor = sfgcolor
            frm.Show()
            frm.Refresh()
            Thread.Sleep(2000)
            Dim cnt As Integer = 0
            current_hour = newhour
            Dim sql As String = ""
            Dim cell_value As String = ""
            Try
                For cnt = 0 To dgvCycleHistory.Rows.Count - 1
                    cell_value = dgvCycleHistory.Rows(cnt).Cells(0).Value
                    sql = "insert into cycle_history (cycle_type,description,details,job_number,cycle_time) values ('hour_shift','Hour Shift','" & cell_value & "','" & lblJobNumber.Text & "','" & Now & "')"
                    Dim cmd As New OleDbCommand(sql, cn)
                    If cmd.ExecuteNonQuery < 1 Then
                        errlog.WriteError("Error updating cycle_history with time shift.", "SaveAndShiftTimeGraph")
                    End If
                    cmd.Dispose()
                Next
            Catch ex As Exception
                errlog.WriteError("Error updating cycle_history with time shift: " & ex.Message, "SaveAndShiftTimeGraph")
            End Try
            frm.Dispose()
            LoadCycleGrid()
            SetGrid()
        End Sub

It's the last two sub calls that are having the problem.  How do I call the sub LoadCycleGrid safely in this thread?  I call this sub from the timer function when the hour changes.
0
 
Jeff CertainCommented:
I'd need to see the code for LoadCycleGrid and SetGrid.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
ddepuemdAuthor Commented:
I can't see why but here they are:


    Private Sub LoadCycleGrid()
        Dim sql As String = ""
        Try
            'load what cycles we have out there
            sql = "select * from cycle_history order by cycle_time"
            Dim cmd As New OleDbCommand(sql, cn)
            dbAdaptr.SelectCommand = cmd
            dbAdaptr.Fill(dsCycles)
            'do we have any?
            If dsCycles.Tables(0).Rows.Count > 0 Then
                'we have some records

            End If
            'build table
            Dim dt As New DataTable
            For rw As Integer = 0 To 7
                dt.Columns.Add("00:00" & rw)
            Next
            Dim drow As DataRow
            For rw As Integer = 0 To 42
                drow = dt.NewRow
                For col1 As Integer = 0 To 7
                    drow(col1) = "            "
                Next
                dt.Rows.Add(drow)
            Next

            Try
                dsCycles.Tables.RemoveAt(1)
            Catch ex As Exception

            End Try
            dsCycles.Tables.Add(dt)
            dgvCycleHistory.DataSource = dsCycles.Tables(1)
            Dim spc As Integer = dgvMainGrid.Left - 10
            Dim col_wid As Integer = CInt((dgvMainGrid.Left - 10) / 9)
            dgvCycleHistory.Location = New Point(34, dgvMainGrid.Location.Y)
            dgvCycleHistory.Width = 756
            dgvCycleHistory.Height = dgvMainGrid.Height
            dgvCycleHistory.ScrollBars = ScrollBars.None
            For rw As Integer = 0 To dgvCycleHistory.Rows.Count - 1
                dgvCycleHistory.Rows(rw).Height = 15
            Next
            Dim header_style As New DataGridViewCellStyle
            With header_style
                .BackColor = Color.Silver
                .ForeColor = Color.DarkRed
                .Alignment = DataGridViewContentAlignment.MiddleCenter
            End With

            dgvCycleHistory.ColumnHeadersHeight = 78
            dgvCycleHistory.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None
            For rw As Integer = 0 To dgvCycleHistory.Columns.Count - 1
                dgvCycleHistory.Columns(rw).HeaderCell.Style = header_style
            Next
            For col1 As Integer = 0 To dgvCycleHistory.Columns.Count - 1
                dgvCycleHistory.Columns(col1).Width = 94
            Next
            'make buttons in headers
            'column one
            Dim btn As New Button
            btn.Text = "History of Production" & vbCrLf & "For This Job"
            Dim rect As Rectangle
            rect = dgvCycleHistory.GetCellDisplayRectangle(0, -1, True)
            Dim x As Integer = rect.Left
            Dim y As Integer = rect.Top
            x = (x + 2)
            y = (y + 2)
            btn.Location = New Point(x, y)
            btn.Tag = "1"
            dgvCycleHistory.Controls.Add(btn)
            dgvCycleHistory.Columns(0).HeaderText = ""
            btn.Size = New Size(rect.Width - 4, rect.Height - 20)
            AddHandler btn.Click, AddressOf HandledgvCycleButtonClick

            'second col
            btn = New Button
            btn.Text = "History of Production" & vbCrLf & "For This Machine"
            rect = dgvCycleHistory.GetCellDisplayRectangle(1, -1, True)
            x = rect.Left
            y = rect.Top
            x = (x + 2)
            y = (y + 2)
            btn.Location = New Point(x, y)
            btn.Tag = "1"
            dgvCycleHistory.Controls.Add(btn)
            dgvCycleHistory.Columns(1).HeaderText = ""
            btn.Size = New Size(rect.Width - 4, rect.Height - 20)
            AddHandler btn.Click, AddressOf HandledgvCycleButtonClick

            'third col
            btn = New Button
            btn.Text = "10 Cycle"
            rect = dgvCycleHistory.GetCellDisplayRectangle(2, -1, True)
            x = rect.Left
            y = rect.Top
            x = (x + 2)
            y = (y + 2)
            btn.Location = New Point(x, y)
            btn.Tag = "1"
            dgvCycleHistory.Controls.Add(btn)
            dgvCycleHistory.Columns(2).HeaderText = ""
            btn.Size = New Size(rect.Width - 4, rect.Height - 20)
            AddHandler btn.Click, AddressOf HandledgvCycleButtonClick

            'fourth col
            btn = New Button
            btn.Text = "100 Cycle"
            rect = dgvCycleHistory.GetCellDisplayRectangle(3, -1, True)
            x = rect.Left
            y = rect.Top
            x = (x + 2)
            y = (y + 2)
            btn.Location = New Point(x, y)
            btn.Tag = "1"
            dgvCycleHistory.Controls.Add(btn)
            dgvCycleHistory.Columns(3).HeaderText = ""
            btn.Size = New Size(rect.Width - 4, rect.Height - 20)
            AddHandler btn.Click, AddressOf HandledgvCycleButtonClick

            'fifth col
            btn = New Button
            btn.Text = "8 Hours"
            rect = dgvCycleHistory.GetCellDisplayRectangle(4, -1, True)
            x = rect.Left
            y = rect.Top
            x = (x + 2)
            y = (y + 2)
            btn.Location = New Point(x, y)
            btn.Tag = "1"
            dgvCycleHistory.Controls.Add(btn)
            dgvCycleHistory.Columns(4).HeaderText = ""
            btn.Size = New Size(rect.Width - 4, rect.Height - 20)
            AddHandler btn.Click, AddressOf HandledgvCycleButtonClick

            'sizth col
            btn = New Button
            btn.Text = "24 Hours"
            rect = dgvCycleHistory.GetCellDisplayRectangle(5, -1, True)
            x = rect.Left
            y = rect.Top
            x = (x + 2)
            y = (y + 2)
            btn.Location = New Point(x, y)
            btn.Tag = "1"
            dgvCycleHistory.Controls.Add(btn)
            dgvCycleHistory.Columns(5).HeaderText = ""
            btn.Size = New Size(rect.Width - 4, rect.Height - 20)
            AddHandler btn.Click, AddressOf HandledgvCycleButtonClick

            'seventh col
            btn = New Button
            btn.Text = "24 Days"
            rect = dgvCycleHistory.GetCellDisplayRectangle(6, -1, True)
            x = rect.Left
            y = rect.Top
            x = (x + 2)
            y = (y + 2)
            btn.Location = New Point(x, y)
            btn.Tag = "1"
            dgvCycleHistory.Controls.Add(btn)
            dgvCycleHistory.Columns(6).HeaderText = ""
            btn.Size = New Size(rect.Width - 4, rect.Height - 20)
            AddHandler btn.Click, AddressOf HandledgvCycleButtonClick

            dgvCycleHistory.Columns(7).HeaderText = ""

            'make time labels
            Dim hr As Integer = Now.Hour + 1
            If hr > 24 Then hr = 0
            For ccol As Integer = 0 To dgvCycleHistory.Columns.Count - 2
                Dim hrs As String = Now.Hour.ToString
                If hr < 10 Then
                    hrs = "0" & hr & ":00"
                Else
                    hrs = hr & ":00"
                End If
                rect = dgvCycleHistory.GetCellDisplayRectangle(ccol, -1, True)
                x = rect.Right
                Dim lbl As New Label
                lbl.Text = hrs
                lbl.TextAlign = ContentAlignment.MiddleCenter
                lbl.Font = New Font("Microsoft Sans Serif", 7)
                Dim g As Graphics = dgvCycleHistory.CreateGraphics
                Dim len As SizeF = g.MeasureString(lbl.Text, lbl.Font)
                lbl.Size = New Size(len.Width + 2, 8)
                lbl.Location = New Point(x - (lbl.Width / 2), rect.Bottom - 10)
                dgvCycleHistory.Controls.Add(lbl)
                hr += 1
                If hr > 24 Then
                    hr = 0
                End If
            Next
            For rw As Integer = 0 To dgvCycleHistory.Rows.Count - 1
                For col As Integer = 0 To dgvCycleHistory.Columns.Count - 1
                    dgvCycleHistory.Rows(rw).Cells(col).Style.Font = New Font("Microsoft Sans Serif", 12, FontStyle.Bold)
                Next
            Next
            cmd.Dispose()
        Catch ex As Exception
            Dim str As String = ex.Message
        End Try
    End Sub

    Private Sub SetGrid()
        dgvMainGrid.Visible = False
        dgvMainGrid.Visible = True
        dgvCycleHistory.Visible = False
        dgvCycleHistory.Visible = True
        For rw As Integer = 0 To dgvCycleHistory.Rows.Count - 1
            dgvCycleHistory.Rows(rw).Height = 15
        Next
        PaintLines()
    End Sub

    'paint solid lines separating groups
    Private Sub PaintLines()
        For rw As Integer = 0 To dgvMainGrid.Rows.Count - 1
            Select Case rw
                Case 2, 5, 8, 11, 14, 17, 20, 23, 29
                    Try
                        Dim g As Graphics = dgvMainGrid.CreateGraphics()
                        Dim p As Pen = New Pen(Color.Red, 1)
                        Dim rect1 As Rectangle, rect2 As Rectangle
                        rect1 = dgvMainGrid.GetCellDisplayRectangle(0, rw, True)
                        rect2 = dgvMainGrid.GetCellDisplayRectangle(dgvMainGrid.Columns.Count - 1, rw, True)
                        Dim linefrom As Point, lineto As Point
                        linefrom.X = rect1.Left
                        linefrom.Y = rect1.Bottom - 2
                        lineto.X = rect2.Right
                        lineto.Y = rect2.Bottom - 2
                        g.DrawLine(p, linefrom, lineto)
                    Catch ex As Exception
                        errlog.WriteError("Error in paint lines: " & ex.Message, "PaintLines")
                    End Try
            End Select
        Next
        For rw As Integer = 0 To dgvCycleHistory.Rows.Count - 1 Step 2
            Try
                Dim g As Graphics = dgvCycleHistory.CreateGraphics()
                Dim p As Pen = New Pen(Color.Black, 1)
                Dim rect1 As Rectangle, rect2 As Rectangle
                rect1 = dgvCycleHistory.GetCellDisplayRectangle(0, rw, True)
                rect2 = dgvCycleHistory.GetCellDisplayRectangle(dgvCycleHistory.Columns.Count - 1, rw, True)
                Dim linefrom As Point, lineto As Point
                linefrom.X = rect1.Left
                linefrom.Y = rect1.Bottom - 2
                lineto.X = rect2.Right
                lineto.Y = rect2.Bottom - 2
                g.DrawLine(p, linefrom, lineto)
            Catch ex As Exception
                errlog.WriteError("Error in paint lines: " & ex.Message, "PaintLines")
            End Try
        Next
    End Sub
0
 
Jeff CertainCommented:
Well.... the reason I wanted to see those is that I usually call Invoke at the framework method level.

Let's try this. Replace your calls to LoadCycleGrid and SetGrid with a call to LoadAndSetGrid. (I'm assuming these are only called together. Otherwise, we'll need to be finer-grained in our approach). Then add this code:

    Private Delegate Sub LoadAndSetGridCallback()
    Private Sub LoadAndSetGrid()
        If Me.InvokeRequired Then
            Dim d As New LoadAndSetGridCallback(AddressOf LoadAndSetGrid)
            Me.Invoke(d, New Object() {})
        Else
            LoadCycleGrid()
            SetGrid()
        End If
    End Sub

Open in new window

0
 
ddepuemdAuthor Commented:
Excellent!!!  I understood the method to update a label or some other control.  But to call a sub was escaping me.  I think I can get the rest from here.  Thank you!
0
 
Jeff CertainCommented:
Glad to help.
0

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now