代码之家  ›  专栏  ›  技术社区  ›  G_Hosa_Phat

具有响应性表单的长时间运行流程-性能改进

  •  0
  • G_Hosa_Phat  · 技术社区  · 7 年前

    Using COPYReader As NpgsqlCopyTextReader = CType(CIADB.DBConnection.BeginTextExport(COPYSQL), NpgsqlCopyTextReader)
        With COPYReader
            Dim stopWatch As New Stopwatch
            Dim ts As TimeSpan
            Dim elapsedTime As String
    
            ' ** FIRST ATTEMPT
            stopWatch.Start()
            Dim BufferText As String = .ReadLine
    
            Do While Not BufferText Is Nothing
                CurrentPosition += 1
                OutputFile.WriteLine(BufferText)
    
                If Not UpdateForm Is Nothing Then
                    UpdateForm.UpdateProgress(Convert.ToInt32((CurrentPosition / MaxRecords) * 100))
                End If
    
                BufferText = .ReadLine
            Loop
    
            OutputFile.Flush()
            OutputFile.Close()
    
            stopWatch.Stop()
            ts = stopWatch.Elapsed
            elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10)
    
            ' ** FIRST ATTEMPT RESULTS
            ' ** Records Retrieved: 65358
            ' ** Time To Complete: 2:12.07
            ' ** Lines Written: 65358
            ' ** File Size: 8,166 KB
    
            ' ** SECOND ATTEMPT
            stopWatch.Start()
    
            Using TestOutputFile As New IO.StreamWriter(DestinationFile.FullName.Replace(".TXT", "_TEST.TXT"), False)
                TestOutputFile.Write(.ReadToEndAsync.Result)
            End Using
    
            stopWatch.Stop()
            ts = stopWatch.Elapsed
            elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10)
    
            ' ** SECOND ATTEMPT
            ' ** Records Retrieved: 65358
            ' ** Time To Complete: 1:04.01
            ' ** Lines Written: 65358
            ' ** File Size: 8,102 KB
        End With
    End Using
    

    我对每种方法都进行了多次测试,得出了几乎相同的结果。 第一次尝试 第二次尝试

    UpdateForm.UpdateProgress 使用的方法 (用于保持表单响应并显示导出的当前进度)将导致流程花费更长的时间,因为表单更新和其他相关操作,更不用说逐行写入文件了。这就是为什么我希望通过在一行代码中进行完全转储来减少额外调用的数量。问题是,如果我使用“一行程序”,在流程完成之前,表单完全没有响应。

    我已经试着把代码从 第二次尝试 Async 方法,但我 通常不熟悉异步方法,所以我(显然)做得不对:

    Private Async Sub OutputToFile(ByVal COPYReader As NpgsqlCopyTextReader, ByVal DestinationFile As IO.FileInfo)
        ' ** METHOD 3
        Using TestOutputFile As New IO.StreamWriter(DestinationFile.FullName.Replace(".TXT", "_TEST.TXT"), False)
            Await TestOutputFile.WriteAsync(COPYReader.ReadToEndAsync.Result)
        End Using
    
        ' ** METHOD 3 RESULTS
        ' ** Records Retrieved: 65358
        ' ** Time To Complete: 0:15.07
        ' ** Lines Written: 34
        ' ** File Size: 4 KB
    End Sub
    

    还有一件事要提:我试着把所有这些都搬到一个 BackgroundWorker UpdateForm.UpdateProgress 使申请完全跳过实际倾销过程的方法。目前,我已经放弃了尝试将其放到另一个线程上,但我仍然愿意接受其他建议。这实际上是我正在丢弃的一张较小的桌子,所以我并不期待其中一张较大的桌子能做什么。

    UpdateForm 类,该类已在库中实现,以便跨其他应用程序重用:

    Imports System.Windows.Forms
    
    Namespace Common
        Public Class FormHandler
            Implements IDisposable
    
            Public Property ApplicationForm As Form
            Public Property ApplicationStatusLabel As Label
            Public Property ApplicationToolStripLabel As ToolStripStatusLabel
            Public Property ApplicationProgressBar As ProgressBar
    
            Private LabelVisibleState As Boolean = True
            Private ProgressBarVisibleState As Boolean = True
            Private CurrentStatusText As String
            Private CurrentProgress As Integer
    
            Public Sub New(ByVal AppForm As Form)
                ApplicationForm = AppForm
            End Sub
    
            Public Sub New(ByVal StatusLabel As Label, ByVal Progress As ProgressBar)
                ApplicationStatusLabel = StatusLabel
                ApplicationToolStripLabel = Nothing
                ApplicationProgressBar = Progress
                ApplicationForm = ApplicationProgressBar.Parent.FindForm
    
                LabelVisibleState = StatusLabel.Visible
                ProgressBarVisibleState = Progress.Visible
    
                With ApplicationProgressBar
                    .Minimum = 0
                    .Maximum = 100
                    .Value = 0
                    .Visible = True
                End With
    
                With ApplicationStatusLabel
                    .Visible = True
                    .Text = ""
                End With
            End Sub
    
            Public Sub New(ByVal StatusLabel As ToolStripStatusLabel, ByVal Progress As ProgressBar)
                ApplicationToolStripLabel = StatusLabel
                ApplicationStatusLabel = Nothing
                ApplicationProgressBar = Progress
                ApplicationForm = ApplicationProgressBar.Parent.FindForm
    
                LabelVisibleState = StatusLabel.Visible
                ProgressBarVisibleState = Progress.Visible
    
                With ApplicationProgressBar
                    .Minimum = 0
                    .Maximum = 100
                    .Value = 0
                    .Visible = True
                End With
    
                With ApplicationToolStripLabel
                    .Visible = True
                    .Text = ""
                End With
            End Sub
    
            Public Sub New(ByVal AppForm As Form, ByVal StatusLabel As Label, ByVal Progress As ProgressBar)
                ApplicationForm = AppForm
                ApplicationStatusLabel = StatusLabel
                ApplicationToolStripLabel = Nothing
                ApplicationProgressBar = Progress
    
                LabelVisibleState = StatusLabel.Visible
                ProgressBarVisibleState = Progress.Visible
    
                With ApplicationProgressBar
                    .Minimum = 0
                    .Maximum = 100
                    .Value = 0
                    .Visible = True
                End With
    
                With ApplicationStatusLabel
                    .Visible = True
                    .Text = ""
                End With
            End Sub
    
            Public Sub New(ByVal AppForm As Form, ByVal StatusLabel As ToolStripStatusLabel, ByVal Progress As ProgressBar)
                ApplicationForm = AppForm
                ApplicationToolStripLabel = StatusLabel
                ApplicationStatusLabel = Nothing
                ApplicationProgressBar = Progress
    
                LabelVisibleState = StatusLabel.Visible
                ProgressBarVisibleState = Progress.Visible
    
                With ApplicationProgressBar
                    .Minimum = 0
                    .Maximum = 100
                    .Value = 0
                    .Visible = True
                End With
    
                With ApplicationToolStripLabel
                    .Visible = True
                    .Text = ""
                End With
            End Sub
    
            Friend Sub UpdateProgress(ByVal StatusText As String, ByVal CurrentPosition As Integer, ByVal MaxValue As Integer)
                CurrentStatusText = StatusText
                CurrentProgress = Convert.ToInt32((CurrentPosition / MaxValue) * 100)
                UpdateStatus()
            End Sub
    
            Friend Sub UpdateProgress(ByVal StatusText As String, ByVal PercentComplete As Decimal)
                CurrentStatusText = StatusText
                CurrentProgress = Convert.ToInt32(PercentComplete)
                UpdateStatus()
            End Sub
    
            Friend Sub UpdateProgress(ByVal StatusText As String)
                CurrentStatusText = StatusText
                CurrentProgress = 0
                UpdateStatus()
            End Sub
    
            Friend Sub UpdateProgress(ByVal PercentComplete As Decimal)
                CurrentProgress = Convert.ToInt32(PercentComplete)
                UpdateStatus()
            End Sub
    
            Friend Sub UpdateProgress(ByVal CurrentPosition As Integer, ByVal MaxValue As Integer)
                CurrentProgress = Convert.ToInt32((CurrentPosition / MaxValue) * 100)
                UpdateStatus()
            End Sub
    
            Friend Sub ResetProgressUpdate()
                CurrentStatusText = ""
                CurrentProgress = 0
                UpdateStatus()
            End Sub
    
            Private Sub UpdateStatus()
                If Not ApplicationForm Is Nothing Then
                    If ApplicationForm.InvokeRequired Then
                        Dim UpdateInvoker As New MethodInvoker(AddressOf UpdateStatus)
    
                        Try
                            ApplicationForm.Invoke(UpdateInvoker)
                        Catch ex As Exception
                            Dim InvokeError As New ErrorHandler(ex)
    
                            InvokeError.LogException()
                        End Try
                    Else
                        UpdateApplicationProgress(CurrentStatusText)
                    End If
                End If
            End Sub
    
            Friend Sub UpdateApplicationProgress(ByVal ProgressText As String)
                If Not ApplicationForm Is Nothing Then
                    With ApplicationForm
                        If Not ProgressText Is Nothing Then
                            If Not ApplicationStatusLabel Is Nothing Then
                                ApplicationStatusLabel.Text = ProgressText
                            End If
    
                            If Not ApplicationToolStripLabel Is Nothing Then
                                ApplicationToolStripLabel.Text = ProgressText
                            End If
                        End If
    
                        If Not ApplicationProgressBar Is Nothing Then
                            ApplicationProgressBar.Value = CurrentProgress
                        End If
                    End With
    
                    ApplicationForm.Refresh()
                    Application.DoEvents()
                End If
            End Sub
    
            Public Sub Dispose() Implements IDisposable.Dispose
                If Not ApplicationForm Is Nothing Then
                    ApplicationForm.Dispose()
                End If
    
                If Not ApplicationStatusLabel Is Nothing Then
                    ApplicationStatusLabel.Visible = LabelVisibleState
                    ApplicationStatusLabel.Dispose()
                End If
    
                If Not ApplicationToolStripLabel Is Nothing Then
                    ApplicationToolStripLabel.Visible = LabelVisibleState
                    ApplicationToolStripLabel.Dispose()
                End If
    
                If Not ApplicationProgressBar Is Nothing Then
                    ApplicationProgressBar.Visible = ProgressBarVisibleState
                    ApplicationProgressBar.Dispose()
                End If
            End Sub
        End Class
    End Namespace
    

    编辑

    根据@the_lotus评论中的建议,我修改了我的 第一次尝试 稍微检查一下当前进度的值(我声明了 CurrentProgress 变量作为 Integer 戏剧性地 改进了所需的时间:

    ' ** FOURTH ATTEMPT
    Using COPYReader As NpgsqlCopyTextReader = CType(CIADB.DBConnection.BeginTextExport(COPYSQL), NpgsqlCopyTextReader)
        With COPYReader
            Dim stopWatch As New Stopwatch
            Dim ts As TimeSpan
            Dim elapsedTime As String
            Dim CurrentProgress As Integer = 0
    
            stopWatch.Start()
            Dim BufferText As String = .ReadLine
    
            Do While Not BufferText Is Nothing
                CurrentPosition += 1
                OutputFile.WriteLine(BufferText)
    
                ' ** Checks to see if the value of the ProgressBar will actually
                ' ** be changed by the CurrentPosition before making a call to
                ' ** UpdateProgress.  If the value doesn't change, don't waste
                ' ** the call
                If Convert.ToInt32((CurrentPosition / MaxRecords) * 100) <> CurrentProgress Then
                    CurrentProgress = Convert.ToInt32((CurrentPosition / MaxRecords) * 100)
    
                    If Not UpdateForm Is Nothing Then
                        UpdateForm.UpdateProgress(CurrentProgress)
                    End If
                End If
    
                BufferText = .ReadLine
            Loop
    
            OutputFile.Flush()
            OutputFile.Close()
    
            stopWatch.Stop()
            ts = stopWatch.Elapsed
            elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10)
        End With
    End Using
    ' ** FOURTH ATTEMPT RESULTS
    ' ** Records Retrieved: 65358
    ' ** Time To Complete: 0:47.45
    ' ** Lines Written: 65358
    ' ** File Size: 8,166 KB
    


    编辑#2

    读:“复制/粘贴” UpdateProgress 方法,我已经将更改值的测试移到了那里,它似乎在以相同的性能改进运行。同样,为了完整起见,下面是执行实际进度/状态更新所涉及的两个私有方法的代码:

    Private Sub UpdateStatus()
        If Not ApplicationForm Is Nothing Then
            If ApplicationForm.InvokeRequired Then
                Dim UpdateInvoker As New MethodInvoker(AddressOf UpdateStatus)
    
                Try
                    ApplicationForm.Invoke(UpdateInvoker)
                Catch ex As Exception
                    Dim InvokeError As New ErrorHandler(ex)
    
                    InvokeError.LogException()
                End Try
            Else
                UpdateApplicationProgress()
            End If
        End If
    End Sub
    
    Private Sub UpdateApplicationProgress()
        Dim Changed As Boolean = False
    
        If Not ApplicationForm Is Nothing Then
            With ApplicationForm
                If Not CurrentStatusText Is Nothing Then
                    If Not ApplicationStatusLabel Is Nothing Then
                        If ApplicationStatusLabel.Text <> CurrentStatusText Then
                            Changed = True
                            ApplicationStatusLabel.Text = CurrentStatusText
                        End If
                    End If
    
                    If Not ApplicationToolStripLabel Is Nothing Then
                        If ApplicationToolStripLabel.Text <> CurrentStatusText Then
                            Changed = True
                            ApplicationToolStripLabel.Text = CurrentStatusText
                        End If
                    End If
                End If
    
                If Not ApplicationProgressBar Is Nothing Then
                    If ApplicationProgressBar.Value <> CurrentProgress Then
                        Changed = True
                        ApplicationProgressBar.Value = CurrentProgress
                    End If
                End If
            End With
    
            If Changed Then
                ApplicationForm.Refresh()
            End If
    
            Application.DoEvents()
        End If
    End Sub
    

    这样做还有一个额外的好处,就是将一些以前丢失的响应返回到表单。我希望这些代码和信息中至少有一些对其他人有帮助。

    1 回复  |  直到 6 年前
        1
  •  1
  •   the_lotus    7 年前

    您不需要在每次调用时调用UpdateProgress。当百分比没有移动时,这是不必要的。尝试做一个小检查,只在需要时更新百分比。

    第二次尝试也可能更快,因为它不会进入数据库。数据可以被缓存。