首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >具有响应式形式的长时间运行过程-性能改进

具有响应式形式的长时间运行过程-性能改进
EN

Stack Overflow用户
提问于 2018-11-21 18:05:12
回答 1查看 52关注 0票数 1

因此,我正在为我的内部应用程序开发一个库,该应用程序与我们的PostgreSQL数据库进行交互(以及其他许多事情)。目前的一个要求是,这个库可以将数据从数据库转储到文件中。我有一些有用的东西,但是我一直在努力提高它的性能。这就是我现在要看的:

代码语言:javascript
复制
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方法中,但总的来说,我对异步方法非常陌生,因此(显然)做得不对:

代码语言:javascript
复制
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类,用于跨其他应用程序的可重用性:

代码语言:javascript
复制
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注释中的建议,我首先修改了@the_lotus第一次尝试,以检查当前进度的值(我将CurrentProgress变量声明为Integer),它改进了所需的时间:

代码语言:javascript
复制
' ** 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方法时必须重新键入的代码量(读:“复制/粘贴”),我将更改值的测试移到了那里,并且它似乎具有相同的性能改进。同样,为了完整起见,下面是执行实际进度/状态更新所涉及的两个私有方法的代码:

代码语言:javascript
复制
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

这样做还带来了额外的好处,即返回以前丢失的表单的一些响应性。我希望这些代码和信息至少能对外面的人有所帮助。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-11-21 18:34:11

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

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53418109

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档