VB.Net 异步 UI 线程问题

VB.Net Asynchronous UI Thread Issue

我有一个 Windows 表单,它有一个 Telerik RadGridView,其中有一堆记录要导入到我们的 SQL 数据库中。 RadGridView 仅向用户显示将要导入的记录,以防他们在导入记录之前看到可以解决的易于修复的错误。

我在Winform窗体上有如下控件:

rgImport - Telerik RadGridView 控件
btnSubmit - 按钮控件
ProgressBar1 - 导入记录时的进度条
Label1 - 一些文本告诉用户查看 rgImport

中显示的记录

我正在寻找其中的一些功能 Async/Await,因此 UI 不会被锁定,并且进度条会显示已导入记录的百分比:

对这些函数的两次调用:
DataAPI.HR_Payroll_TimeCards_Insert(博士)
DataAPI.HR_Payroll_TimeCards_Insert_双(博士)
除了将记录注入我们的数据库服务器表和 return 一个关于成功还是失败的布尔值之外什么都不做

Private Sub Import_Payroll_Review_Load(sender As Object, e As EventArgs) Handles Me.Load
        rgImport.DataSource = DataAPI.HR_Payroll_Import_GetData()
        ProgressBar1.Minimum = 0
        ProgressBar1.Maximum = 100
        ProgressBar1.Visible = False
End Sub

DataAPI.HR_Payroll_Import_GetData() - 只是 returns 一个数据表,用于加载带有记录的 RadGridView

Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
   
   Select Case MsgBox("Are you sure you are ready to import?", MsgBoxStyle.YesNo, "Import Records")
      Case MsgBoxResult.Yes

        'Import the records from the import table
        Dim dt As DataTable = DataAPI.HR_Payroll_Import_GetRecordsToImport()
        Dim iRows As Long = 1
        Dim iTotalRows As Long = dt.Rows.Count

        'CallTheInsertMethodHere

        For Each dr As DataRow In dt.Rows

            If dr("AfterHours") = 0 Then
                'This is standard time, so import it into the [TimeCards.Current] table
                If DataAPI.HR_Payroll_TimeCards_Insert(dr) Then
                    'Success
                    Debug.Print("Inserted Row into TimeCard.Current")
                Else
                    'Failed
                    Debug.Print("Failed to insert row into TimeCard.Current")
                End If
            Else
                'This is double time, so import it into the [TimeCards.Current.Double] table
                If DataAPI.HR_Payroll_TimeCards_Insert_Double(dr) Then
                    'Success
                    Debug.Print("Inserted Row into TimeCard.Current.Double")
                Else
                    'Failed
                    Debug.Print("Failed to insert row into TimeCard.Current.Double")
                End If
            End If

            iRows += 1

        Next
        MsgBox("Inserted " & iRows - 1 & " rows", vbInformation, "Success")

    Case Else
        'User clicked on no, so exit
        Exit Sub
  End Select
End Sub

我只是在寻找 help/direction 如何分解它以及在何处添加 Async/Await 以允许 UI 自由显示百分比的记录已使用 ProgressBar 插入(总记录在变量 iTotalRows 中)

感谢您抽出宝贵时间审阅此内容,以及您可能向我提供的任何意见以使此工作正常进行。

这是 Async/Await 的基本实现,让您了解 UI 在等待 DoWorkAsync 时如何仍然响应,但在 运行 DoWork 时则不然。我包括了 this Stephen Cleary answer

的进展
Private Function DoWorkAsync(progress As IProgress(Of Integer)) As Task(Of Boolean)
    Return Task.Run(Function() DoWork(progress))
End Function

Private Function DoWork(progress As IProgress(Of Integer)) As Boolean
    Dim i As Integer
    While i < 100
        i += (New Random).Next(5)
        Threading.Thread.Sleep(50)
        progress.Report(i)
    End While
    Return True
End Function

Private Async Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
    Dim progress = New Progress(Of Integer)(
        Sub(value)
            ProgressBar1.Value = Math.Max(Math.Min(value, ProgressBar1.Maximum), ProgressBar1.Minimum)
            Console.WriteLine(value)
        End Sub)
    If MessageBox.Show("Run Async?", "Confirm", MessageBoxButtons.YesNo) = DialogResult.Yes Then
        Await DoWorkAsync(progress)
    Else
        DoWork(progress)
    End If
    MessageBox.Show("Done")
End Sub

您基本上可以将所有工作放入 DoWork 中,使用成功逻辑(这就是我返回布尔值的原因)并且它不会阻塞 UI。我不会担心为 DataAPI.HR_Payroll_Import_GetData()DataAPI.HR_Payroll_Import_GetRecordsToImport()DataAPI.HR_Payroll_TimeCards_Insert()DataAPI.HR_Payroll_TimeCards_Insert_Double(dr) 制作单独的函数,除非您的代码的其他部分需要这种粒度。

您可以添加一个 CancellationToken,并使用您自己的代码完成工作,它可能看起来像这样

Private Function DoWorkAsync(progress As IProgress(Of Integer), cancellationToken As Threading.CancellationToken) As Task(Of Boolean)
    Return Task.Run(Function() DoWork(progress, cancellationToken))
End Function

Private Function DoWork(progress As IProgress(Of Integer), cancellationToken As Threading.CancellationToken) As Boolean
    Try
        'Import the records from the import table
        Dim dt As DataTable = DataAPI.HR_Payroll_Import_GetRecordsToImport()
        Dim iRows As Long = 1
        Dim iTotalRows As Long = dt.Rows.Count
        'CallTheInsertMethodHere
        For Each dr As DataRow In dt.Rows
            If dr("AfterHours") = 0 Then
                'This is standard time, so import it into the [TimeCards.Current] table
                If DataAPI.HR_Payroll_TimeCards_Insert(dr) Then
                    'Success
                    Debug.Print("Inserted Row into TimeCard.Current")
                Else
                    'Failed
                    Debug.Print("Failed to insert row into TimeCard.Current")
                End If
            Else
                'This is double time, so import it into the [TimeCards.Current.Double] table
                If DataAPI.HR_Payroll_TimeCards_Insert_Double(dr) Then
                    'Success
                    Debug.Print("Inserted Row into TimeCard.Current.Double")
                Else
                    'Failed
                    Debug.Print("Failed to insert row into TimeCard.Current.Double")
                End If
            End If
            iRows += 1
            progress.Report(CInt(100 * iRows / iTotalRows))
            cancellationToken.ThrowIfCancellationRequested()
        Next
        Return True
    Catch ex As Exception
        Debug.Print(ex.Message)
        Return False
    End Try
End Function

Private Async Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
    Dim progress As IProgress(Of Integer) = New Progress(Of Integer)(
    Sub(value)
        ProgressBar1.Value = Math.Max(Math.Min(value, ProgressBar1.Maximum), ProgressBar1.Minimum)
        Console.WriteLine(value)
    End Sub)
    cancellationSource = New Threading.CancellationTokenSource()
    Dim cancellationToken = cancellationSource.Token
    cancellationToken.Register(Sub() progress.Report(0))
    If MessageBox.Show("Run Async?", "Confirm", MessageBoxButtons.YesNo) = DialogResult.Yes Then
        Await DoWorkAsync(progress, cancellationToken)
    Else
        DoWork(progress, cancellationToken)
    End If
    progress.Report(0)
    If cancellationToken.IsCancellationRequested Then
        MessageBox.Show("Cancelled")
    Else
        MessageBox.Show("Done")
    End If
End Sub

Private cancellationSource As New Threading.CancellationTokenSource()

Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
    cancellationSource.Cancel()
End Sub

感谢Stephen Cleary for the CancellationToken,再次