Secondary thread causes “Application has stopped working” crashes even when invoking


April 2019


6 time


I have an application which has a form with a DataGridView bound to a BindingSource, which is bound to a DataTable:

bsList.DataSource = dsData
bsList.DataMember = "List"
dgvList.DataSource = bsList

The underlying data which populates dsData.Tables("List") can change whilst the user is working so to combat this I have a background thread which routinely checks the database for changes and updates dsData.Tables("List"). It also changes the colour of any row where another user is currently working.

However, users report that when this background updating functionality is enabled the application routinely CTDs with no application error message. I have been unable to reproduce this and my attempt to log the crashes via writing to a log file in Private Sub MyApplication_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Handles Me.UnhandledException hasn't worked as the log file is never written to, suggesting this event is never triggered.

The thread is instantiated like this:

LiveUpdating = New Thread(AddressOf UpdateUserLocation) With {.IsBackground = True}

This is the UpdateUserLocation sub:

Public Sub UpdateUserLocation()

Do While My.Settings.customBackgroundUpdating = True And formLoaded = True

dtUsers = CLS_USERS.GetUsersSequence(winUser)
dtProgress = DAC.GetProgress()

For Each CandRow As DataRow In dsHHData.Tables("List").Rows

    Dim CandReadDate As Date
    Dim CandRowNextRead As String = DBNull.Value.ToString

    If Not (CandRow("NEXT READ").ToString = DBNull.Value.ToString) Then
        If Date.TryParse(CandRow("NEXT READ").ToString, CandReadDate) Then
            CandRowNextRead = CandReadDate.ToString("dd/MM/yyyy")
        End If
    End If

    Dim CandRowSending As String = TryCast(CandRow("SENDING"), String)
    Dim CandRowNotes As String = TryCast(CandRow("NOTES"), String)

    For Each NewRow As DataRow In dtUsers.Rows
        If CandRow("SQ").ToString = NewRow("SQ").ToString Then
        End If

    For Each ProgressRow As DataRow In dtProgress.Rows
        If CandRow("SQ").ToString = ProgressRow("SQ").ToString Then

            Dim NextReadDate As Date
            Dim ProgressRowNextRead As String = DBNull.Value.ToString

            If Not (ProgressRow("NEXT READ").ToString = DBNull.Value.ToString) Then
                If Date.TryParse(ProgressRow("NEXT READ").ToString, NextReadDate) Then
                    ProgressRowNextRead = NextReadDate.ToString("dd/MM/yyyy")
                End If
            End If

            Dim ProgressRowSending As String = TryCast(ProgressRow("SENDING"), String)
            Dim ProgressRowNotes As String = TryCast(ProgressRow("NOTES"), String)

            If CandRow("SQ").ToString = ProgressRow("SQ").ToString Then

                If CandRowSending <> ProgressRowSending Then
                    BeginInvoke(New UpdateDataTableDelegate(AddressOf UpdateDataTableSending), CandRow, ProgressRowSending)
                End If

                If CandRowNextRead <> ProgressRowNextRead Then
                    BeginInvoke(New UpdateDataTableDelegate(AddressOf UpdateDataTableNextRead), CandRow, ProgressRowNextRead)
                End If

                If CandRowNotes <> ProgressRowNotes Then
                    BeginInvoke(New UpdateDataTableDelegate(AddressOf UpdateDataTableNotes), CandRow, ProgressRowNotes)
                End If

            End If
        End If

    New MethodInvoker(
        For Each dgv_row As DataGridViewRow In dgv.Rows
            If UserLocations.Contains(dgv_row.Cells("SQ").Value.ToString) Then
                dgv.DefaultCellStyle.BackColor = My.Settings.customRowHighlight
                dgv.DefaultCellStyle.BackColor = Nothing
            End If
    End Sub))

Thread.Sleep(My.Settings.customRefreshRate * 1000)


End Sub

The subs that do the DataTable update are like this:

Private Delegate Sub UpdateDataTableDelegate(ByVal CandRow As DataRow, ByVal ProgressRow As String)
Private Sub UpdateDataTableSending(ByVal CandRow As DataRow, ByVal ProgressRowSending As String)
        CandRow("SENDING") = ProgressRowSending
    End Sub

I know this is not the best way to handle a multi-user environment but the nature of this work requires that all people can access and see the same data. I could force them to refresh regularly but that seems very intrusive.

The crashes only occur when this thread is running and the crashes are regular (and not instant) but I cannot seem to reproduce them and the application is very stable otherwise.

There must be some cross-threading issue but I can't work how when all of the updates to the DataTable or DataGridView are done via a BeginInvoke on the main UI thread.

EDIT: I've just realised that even though I am doing the queries and most of the heavy lifting in the background thread, the updates are stilled called on the main UI thread which would lock the thread. This would be particularly noticeable if there were a lot of updates... Because each one is called individually. If the UI lock up was long enough, and the user was clicking on stuff, would this cause Windows to treat the application as unresponsive and crash it? If so, is there a better way I could handle these updates?

Any help with resolving this would be enormously appreciated.


0 answers