使用 Mutex Multithread 时更新 Form1 上的进度条

Update Progress bar on Form1, when using a Mutex Multithread

提问人:Andy Andromeda 提问时间:3/25/2023 最后编辑:Ken WhiteAndy Andromeda 更新时间:3/25/2023 访问量:41

问:

我正在尝试在将数据写入 MS Access 数据库时更新进度条。为了更快地写入数据库。我已将数据表中的数据分解为 1000 行,总共大约有 8500 行。然后,批处理使用互斥线程方法来加快该过程。

我目前在 Form1 上有一个按钮,用于启动写入数据的过程。

Private Sub PrepareData_Click(sender As Object, e As EventArgs) Handles PrepareData.Click
        PrepareData.BackColor = Color.SlateGray
        Data_Preperation_Tasks()
End Sub

对于上下文,也会发生这种情况,

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'Initialize the BackgroundWorker
        bgWorker.WorkerReportsProgress = True
        bgWorker.WorkerSupportsCancellation = True
        bgWorker.RunWorkerAsync()
End Sub
Private Sub bgWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgWorker.ProgressChanged
        If Me.InvokeRequired Then
            Me.Invoke(Sub()
                          Me.ProgressBar1.Value = e.ProgressPercentage
                      End Sub)
            Return
        Else
            ProgressBar1.Value = e.ProgressPercentage
            ProgressBar1.Refresh()
        End If
End Sub

然后调用 sub:

Private Sub Data_Preperation_Tasks()
    'other code here that checks things

          Call Write_ProductDetails_ToDatabase_MutexMethod()
End Sub

以上字幕属于公开课表格 1 Write_ProductDetails_ToDatabase_MutexMethod() 位于 Module1 中

Module Module1

 Private Sub UpdateProgressBar(ByVal value As Integer)
        If Form1.IsHandleCreated Then
            Form1.Invoke(Sub()
                             Form1.ProgressBar1.Value = value
                             Form1.ProgressBar1.Refresh()
                         End Sub)
        Else
            Form1.ProgressBar1.Value = value
            Form1.ProgressBar1.Refresh()
        End If
    End Sub

    Sub Write_ProductDetails_ToDatabase_MutexMethod()

        Form1.Action3.Text = "Writing Product Details To Database"
        Form1.Action3.BackColor = Color.Orange
        Form1.Action3.Visible = True

        Dim sAppPath As String
        sAppPath = System.Windows.Forms.Application.StartupPath

        Dim connectionString As String = "PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=" & sAppPath & "\BatchNumberUpload.MDB"
        Dim mutexName As String = "database_mutex"
        Dim mutex As Mutex = New Mutex(False, mutexName)

        Dim batchSize As Integer = 1000
        Dim threadCount As Integer = (InventoryTable.Rows.Count + batchSize - 1) \ batchSize ' Round up to the nearest integer
        Dim countdownEvent As CountdownEvent = New CountdownEvent(threadCount)

        Dim rowCount As Integer = 0 ' Keep track of the number of rows that have been written so far
        Dim totalRowCount As Integer = InventoryTable.Rows.Count

        Form1.ProgressBar1.Minimum = 0
        Form1.ProgressBar1.Maximum = totalRowCount
        Form1.ProgressBar1.Visible = True
       
        'Start the BackgroundWorker
        Form1.bgWorker.RunWorkerAsync()

        For i As Integer = 0 To InventoryTable.Rows.Count - 1 Step batchSize
            Dim batchRows = InventoryTable.Rows.Cast(Of DataRow)().Skip(i).Take(batchSize)

            ' Create a new thread for each batch of rows
            Dim thread = New Thread(Sub()
                                        ' Synchronize access to the database connection using a Mutex
                                        mutex.WaitOne()
                                        Try
                                            Using connection = New OleDbConnection(connectionString)
                                                Using command = New OleDbCommand()
                                                    command.Connection = connection
                                                    connection.Open()

                                                    For Each row In batchRows
                                                        Dim constring As String = "PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=" & sAppPath & "\BatchNumberUpload.MDB"

                                                        Using con As New OleDbConnection(constring)

                                                            Using cmd As New OleDbCommand("INSERT INTO " & "`" & "BatchNumbers" & "`" & " Values(@BatchNumber,@ProductCode,@FirstLine,@SecondLine,@RRP,@SalePrice,@SaleEndDate,@Barcode)", con)


                                                                cmd.Parameters.AddWithValue("@BatchNumber", row.Item("BatchNumber"))
                                                                cmd.Parameters.AddWithValue("@ProductCode", row.Item("ProductCode"))
                                                                cmd.Parameters.AddWithValue("@FirstLine", row.Item("FirstLine"))
                                                                cmd.Parameters.AddWithValue("@SecondLine", row.Item("SecondLine"))
                                                                cmd.Parameters.AddWithValue("@RRP", row.Item("RRP"))
                                                                cmd.Parameters.AddWithValue("@SalePrice", row.Item("SalePrice"))
                                                                cmd.Parameters.AddWithValue("@SaleEndDate", row.Item("SaleEndDate"))
                                                                cmd.Parameters.AddWithValue("@Barcode", row.Item("Barcode"))
                                                                con.Open()

                                                                cmd.ExecuteNonQuery()

                                                                con.Close()

                                                            End Using

                                                        End Using
                                                        rowCount += 1 ' Increment the number of rows that have been written so far
                                                        If rowCount Mod 10 = 0 Then
                                                            Dim progressPercentage As Integer = CInt(rowCount * 100 / totalRowCount)
                                                            Form1.bgWorker.ReportProgress(progressPercentage) ' Report progress to the background worker
                                                        End If

                                                    Next
                                                End Using
                                            End Using
                                        Finally
                                            mutex.ReleaseMutex()
                                            countdownEvent.Signal() ' Signal that the thread has completed
                                        End Try
                                    End Sub)
            thread.Start()
        Next

        ' Wait for all threads to complete before proceeding to the next subroutine
        countdownEvent.Wait()

        UpdateProgressBar(totalRowCount)

        Form1.Action3.Text = "Completed Writing To Database"
        Form1.Action3.BackColor = Color.LimeGreen
        Form1.Action3Tick.Visible = True

        Form1.TimeRemaining.Text = "Completed"

    End Sub



End Module

我的问题是无论我尝试什么,我都无法在 Form1 上恢复 ProgressBar1 以更新/重绘以显示它已经进展。 它将在发生 countdownEvent.Wait() 之后的末尾更新。

此外,当这些踏板运行时,窗体及其所有控件都无响应。我有点理解为什么,并且这些线程没有在 UI 线程上运行。

但是,有没有办法在运行时更新进度条,甚至保持表单响应,以便将其最小化?从示例中,如果在stackoverflow上看到,我无法开始工作。

vb.net 多线程 进度条 互斥锁

评论

0赞 Hursey 3/25/2023
为什么要从已打开报告进度的线程中使用 invoke?为什么不只处理报表处理事件呢?
0赞 jmcilhinney 3/25/2023
您正在使用表单的默认实例,并且不了解它们的工作原理。我建议你读一读这篇文章。如果希望能够在代码中封送对 UI 线程的调用,而该代码无法直接访问在该线程上创建的控件,则应起诉该类,因此应对此进行调查。SynchronizationContext
0赞 Andy Andromeda 3/25/2023
@jmcilhinney我会看看它,看看我是否能理解它。老实说,我真的不明白互斥锁类是如何工作的。
0赞 Andy Andromeda 3/25/2023
@jmcilhinney不幸的是,我无法理解链接中与我正在做的事情相关的内容。我试图研究 SynchronizationContext 类。但我无法适应我的问题。
0赞 Andy Andromeda 3/25/2023
@Hursey我不确定如何实现您的建议,对不起。

答: 暂无答案