Heavy Disk I/O in Background Thread Locks UI

I'm getting heavy disk I/O during a SqlCeEngine.Upgrade() call against a 230MB database--as expected.

The problem is that even though it's running in a BackgroundWorker thread, it still locks up the UI. I've tried Async/Await to no avail. (FWIW, I'll be upgrading the code as time allows.)

I'm pulling my hair out with this. I need to show an indeterminate progress bar as this runs, yet it freezes halfway through no matter what I do. The only solution I can think of is to do the upgrade in a completely separate process, which of course is pretty kludgy. If I have to I will, though.

Excerpted code is below.

Any ideas?

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

Answers


You're using the backgroundworker wrong. From DoWork:

You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the BackgroundWorker events.

It sure looks like you're manipulating the UI from your DoWork handler.


I'd stick with BackgroundWorker for the moment, where it's 100% clear that the upgrade is running on a separate pool thread.

Then I'd investigate exactly what the foreground is trying to do at the point it hangs. You might consider using ProcDump for this.

My guess would be that the foreground is, at some point, making some call into the database layer which then blocks because the upgrade is running. Don't forget that this call could be as a result of pretty much anything (timer, mouse movement whatever), but that once it has blocked it will stall the whole message pump, which will then hang the UI.


Need Your Help

How do I trigger touchevents with an array?

javascript arrays touch-event

In my app a user can start recording and each button they touch will be saved into an array

Getting error _OBJC_CLASS_$_AdMobView while compiling in iPhone SDK 4

iphone admob

I implemented Admob in my application previously in 3.x versions of SDk there i used libAdMobDevice3_0.a frame work. But now i am using 4.o version when i am compiling i am getting the compilation ...