后台线程锁UI中的重磁盘I / O.

时间:2014-02-20 07:47:12

标签: .net multithreading backgroundworker async-await

我正在{230}数据库的SqlCeEngine.Upgrade()调用期间获得大量磁盘I / O - 正如预期的那样。

问题在于即使它在BackgroundWorker线程中运行,它仍然会锁定UI。我试过Async/Await无济于事。 (FWIW,我将在时间允许的情况下升级代码。)

我正用这个把头发拉出来。我需要显示一个不确定的进度条,因为它运行,但无论我做什么,它都会中途冻结。我能想到的唯一解决方案是在一个完全独立的过程中进行升级,这当然非常糟糕。但是,如果我必须这样做。

摘录代码如下。

有什么想法吗?

Friend Class MainForm
  Private Sub itmRestore_Click(Sender As Object, e As EventArgs) Handles itmRestore.Click
    If Utils.MsgQuestion(Bl.Messages.DB_RESTORE_PROMPT) = MsgBoxResult.Yes Then
      ofdRestore.InitialDirectory = Bl.Registry.DownloadFolder
      ofdRestore.FileName = String.Empty

      If ofdRestore.ShowDialog = Windows.Forms.DialogResult.OK Then
        prgCopy.SourcePath = ofdRestore.FileName
        prgCopy.TargetPath = Db.Utils.DatabasePath
        prgCopy.ActionText = "Restoring the database..."

        Me.Cursor = Cursors.WaitCursor
        bgwBackup.RunWorkerAsync(Bl.BackgroundJobs.RestoreDatabase)
      End If
    End If
  End Sub

  Private Sub bgwBackup_DoWork(Sender As Object, e As DoWorkEventArgs) Handles bgwBackup.DoWork
    bgwStartup.ReportProgress(-1, "Loading...")

    Select Case DirectCast(e.Argument, Bl.BackgroundJobs)
      Case Bl.BackgroundJobs.BackupDatabase : Bl.Jobs.Database.BackupDatabase(prgCopy, e)
      Case Bl.BackgroundJobs.RestoreDatabase : Bl.Jobs.Database.RestoreDatabase(prgCopy, e)
      Case Bl.BackgroundJobs.CheckDatabaseVersion : Bl.Jobs.Database.CheckDatabaseVersion(e)
      Case Bl.BackgroundJobs.UpgradeDatabase : Bl.Jobs.Database.UpgradeDatabase(e)
      Case Bl.BackgroundJobs.UpdateDatabaseSchema : Bl.Jobs.Database.UpdateDatabaseSchema(Sender, e)
    End Select
  End Sub

  Private Sub bgwBackup_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgwBackup.ProgressChanged
    UpdateProgress(e.ProgressPercentage, e.UserState)
  End Sub

  Private Sub bgwBackup_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bgwBackup.RunWorkerCompleted
    Dim eNextJob As Bl.BackgroundJobs
    Dim oManager As Bl.Jobs.Manager

    oManager = e.Result

    If e.Error Is Nothing Then
      Select Case oManager.CurrentJob
        Case Bl.BackgroundJobs.BackupDatabase
          eNextJob = Bl.BackgroundJobs.None
          UpdateProgress(0)
          Utils.MsgInformation(Bl.Messages.DB_BACKUP_SUCCESS.ToFormat(vbCrLf, sfdBackup.FileName))

        Case Bl.BackgroundJobs.RestoreDatabase
          eNextJob = Bl.BackgroundJobs.CheckDatabaseVersion
          UpdateProgress(0)
          Utils.MsgInformation(Bl.Messages.DB_RESTORE_SUCCESS)

        Case Bl.BackgroundJobs.CheckDatabaseVersion
          If oManager.Continue Then
            eNextJob = Bl.BackgroundJobs.UpdateDatabaseSchema
          Else
            eNextJob = Bl.BackgroundJobs.UpgradeDatabase
            Utils.MsgInformation(Bl.Messages.DB_UPGRADE_NOTICE)
            UpdateProgress(-1, "Upgrading the database...")
          End If

        Case Bl.BackgroundJobs.UpgradeDatabase
          eNextJob = Bl.BackgroundJobs.UpdateDatabaseSchema
          UpdateProgress(0)
          Utils.MsgInformation(Bl.Messages.DB_UPGRADE_SUCCESS)

        Case Bl.BackgroundJobs.UpdateDatabaseSchema
          eNextJob = Bl.BackgroundJobs.None

      End Select
    Else
      Utils.MsgCritical(Bl.Messages.DB_RESTORE_ERROR.ToFormat(vbCrLf, e.Error.ToString))
      eNextJob = Bl.BackgroundJobs.None
    End If

    If eNextJob = Bl.BackgroundJobs.None Then
      UpdateProgress(0, "Ready")
      txtLastName.Focus()

      Me.Cursor = Cursors.Default
    Else
      bgwBackup.RunWorkerAsync(eNextJob)
    End If
  End Sub

  Private Sub UpdateProgress(ProgressPercentage As Integer)
    UpdateProgress(ProgressPercentage, String.Empty)
  End Sub

  Private Sub UpdateProgress(ProgressPercentage As Integer, StatusText As String)
    If ProgressPercentage = -1 Then
      If prgProgress.Style <> ProgressBarStyle.Marquee Then
        prgProgress.Style = ProgressBarStyle.Marquee
        prgProgress.Value = 0
      End If
    Else
      prgProgress.Style = ProgressBarStyle.Blocks
      prgProgress.Value = Min(ProgressPercentage, 100)
    End If

    If Trim(StatusText).Length > 0 Then
      lblStatus.Text = StatusText
    End If
  End Sub

End Class

Namespace Bl
  Namespace Jobs
    Friend Class Manager
      Public CurrentJob As BackgroundJobs
      Public [Continue] As Boolean

      Public Sub New(CurrentJob As BackgroundJobs, [Continue] As Boolean)
        Me.CurrentJob = CurrentJob
        Me.Continue = [Continue]
      End Sub
    End Class

    Friend Class Database
      Public Shared Sub CheckDatabaseVersion(e As DoWorkEventArgs)
        e.Result = New Manager(e.Argument, Db.Utils.CheckDatabaseVersion)
      End Sub

      Public Shared Sub UpgradeDatabase(e As DoWorkEventArgs)
        Db.Utils.UpgradeDatabase()
        e.Result = New Manager(e.Argument, True)
      End Sub

      Public Shared Sub UpdateDatabaseSchema(Worker As BackgroundWorker, e As DoWorkEventArgs)
        If Db.Versioning.SchemaVersionStatus = Db.Enums.SchemaVersionStates.Newer Then
          Throw New ApplicationException("An incompatible database version has been detected. A newer version of Matrix is required.")
        Else
          Db.Versioning.UpdateSchema(Worker, e)
          e.Result = New Manager(e.Argument, True)
        End If
      End Sub

      Public Shared Sub BackupDatabase(Copier As ProgressCopy.ProgressCopy, e As DoWorkEventArgs)
        Copier.Start()
        e.Result = New Manager(e.Argument, True)
      End Sub

      Public Shared Sub RestoreDatabase(Copier As ProgressCopy.ProgressCopy, e As DoWorkEventArgs)
        Db.Utils.ArchiveDatabase()
        Copier.Start()
        e.Result = New Manager(e.Argument, True)
      End Sub
    End Class
  End Namespace

  Friend Enum BackgroundJobs
    None
    UpgradeDatabase
    UpdateDatabaseSchema
    ValidateCityCode
    CheckSubscription
    CheckOptionsWizard
    CheckDatabaseVersion
    BackupDatabase
    RestoreDatabase
  End Enum
End Namespace

Namespace Db
  Friend Class Utils
    Public Shared Sub UpgradeDatabase()
      Dim _
        sSource,
        sTarget As String

      sSource = ArchiveDatabase()
      sTarget = DatabasePath

      Try
        With New SqlCeEngine(SqlCe.Connection.ConnectionString(sSource))
          .Upgrade(SqlCe.Connection.ConnectionString(sTarget))
        End With

      Catch ex As Exception
        Throw New ApplicationException(ex.Message, ex)

      End Try
    End Sub

    Public Shared Function ArchiveDatabase() As String
      Dim oFile As FileInfo

      Dim _
        sTarget,
        sNow As String

      Dim _
          oSource, _
          oTarget _
        As DirectoryInfo

      Do
        sNow = Now.ToFileNameString(DateStringOptions.IncludeTime)
        oSource = New DirectoryInfo(DatabaseFolder)
        sTarget = Path.Combine(oSource.FullName, "Deleted {0}".ToFormat(sNow))
        oTarget = New DirectoryInfo(sTarget)
      Loop While oTarget.Exists

      oTarget.Create()

      For Each oFile In oSource.GetFiles
        oFile.MoveTo(Path.Combine(oTarget.FullName, oFile.Name))
      Next

      ArchiveDatabase = Path.Combine(oTarget.FullName, Db.Utils.DatabaseFile)
    End Function
  End Class
End Namespace

2 个答案:

答案 0 :(得分:2)

你正在使用背景工作者。来自DoWork

  

您必须小心不要操纵DoWork事件处理程序中的任何用户界面对象。而是通过BackgroundWorker事件与用户界面进行通信。

看起来您正在使用DoWork处理程序操作UI。

答案 1 :(得分:1)

我暂时坚持使用BackgroundWorker,100%明确表示升级是在单独的池线程上运行。

然后我会准确地调查前景在它挂起时要做的事情。您可以考虑使用ProcDump

我的猜测是,在某些时候,前景是对数据库层进行一些调用,然后由于升级正在运行而阻塞。不要忘记这个调用可能是几乎任何事情的结果(计时器,鼠标​​移动等),但是一旦它被阻止它将停止整个消息泵,然后挂起UI。