具有响应形式的长期运行的流程-性能提升

时间:2018-11-21 18:05:12

标签: vb.net npgsql

因此,我正在为我的内部应用程序开发一个库,该库可与PostgreSQL数据库进行交互(还有许多其他功能)。目前的一个要求是,该库可以将数据库中的数据转储到文件中。我有一些工作要做,但我一直在尝试尽可能提高其性能。这是我目前正在查看的内容:

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

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

的两倍

很显然,在 FIRST ATTEMPT 中使用的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注释中的建议,我稍微修改了 FIRST ATTEMPT 以检查当前进度的值(我将CurrentProgress变量声明为{{1} }),它 大大地 缩短了时间:

Integer

当然,与我在每条记录上拨打电话相比,该表格的响应速度“小”,但我认为这值得权衡。


编辑#2

为了使我每次使用' ** 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 方法时都要重新键入( read:“ copy / paste” )的代码量最少,我已经移动了测试该处的值是否已更改,并且似乎在以相同的性能改进进行操作。再次,出于完整性考虑,下面是执行实际进度/状态更新所涉及的两个私有方法的代码:

UpdateProgress

以这种方式进行操作还带来了额外的好处,即可以将一些响应能力返回到以前丢失的表单中。我希望至少其中一些代码和信息对那里的人有所帮助。

1 个答案:

答案 0 :(得分:1)

您无需在每次调用时都调用UpdateProgress。当百分比甚至没有变动时,则没有必要。尝试做一点检查,仅在需要时更新百分比。

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