The Luddite Developer

Monday, 10th August 2009

Using the Background Worker Process for Long Running Processes in WPF and Silverlight

Using the Background Worker Process for Long Running Processes in WPF and Silverlight

Using the Background Worker Process for Long Running Processes in WPF and Silverlight

In a previous article WPF DataBinding using Visual Basic (VB.NET), a problem was encountered when trying to keep the User Interface active during Long Running Processes as a result of a button click.  Either the whole system would hang until the process was completed or the program would crash.

This hang-up is due to the fact that both the Long Running Process and the UI are using the same thread.  The solution is to run the Long Running Process in a separate thread.  The BackgroundWorker component, gives you the ability to execute Long Running Processes in the background asynchronously, on a separate thread from the application’s main UI thread.

I have just started a project which is going to have quite a few of these Long Running Processes and so what I need is a simple implementation of the Background Worker  process which I can expand as required.

The implementation consists of a WPF page with a start button, a cancel button and three labels to let the user know what is happening before, during and after the Long Running Process has been started by the start button.


<Page x:Class="BackGroundWorkerExample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="BackGroundWorkerExample"
    x:Name="BackGroundWorkerExample"  WindowTitle="WPF Luddite Test Bed: Background Worker Example" Width="1024" Height="768">
    <StackPanel>
        <Button x:Name="btnStartBackgroundProcess" Width="300" Height="30" Margin="0 20 0 0"
            Content="Start Background Process">
        </Button>
        <Label x:Name="lblStartedCompleted" Content="" Width="500" Height="30" Margin="0 20 0 0">
        </Label>
        <Label x:Name="lblCounter" Content="" Width="500" Height="30" Margin="0 20 0 0">
        </Label>
        <Label x:Name="lblPercentCompleted" Content="" Width="500" Height="30" Margin="0 20 0 0">
        </Label>
        <Button x:Name="btnCancelBackgroundProcess" Width="300" Height="30" Margin="0 20 0 0"
            Content="Cancel Background Process" IsEnabled="False">
        </Button>

    </StackPanel>
</Page>

The following steps will be required in the code behind:

1.  Define a new BackgroundWorker, create an instance and do some setup.

2. Define the LongRunningProcess – must not reference the User Interface (UI).

3. Define a subroutine to handle the BackgroundWorker DoWork event.

4. Define a subroutine to handle the BackgroundWorker ProgressChanged event.

5. Define a subroutine to handle the BackgroundWorker RunWorkerCompleted event.

6. Define a subroutine to handle the event caused by the Start button being clicked.

7. Define a subroutine to handle the event caused by the Cancel button being clicked.


Imports System.ComponentModel

Partial Public Class BackGroundWorkerExample

    '
    '   Define a new backgroung worker.
    '
    Private WithEvents bw As New BackgroundWorker()
    '
    ' Create new instance of BackGroundWorker and do some setup
    '
    Public Sub New()

        InitializeComponent()

        bw.WorkerSupportsCancellation = True
        bw.WorkerReportsProgress = True

    End Sub

    Private Function LongRunningProcess() As String
        '
        '   The LongRunngProcess should NOT refer to any UI objects.
        '
        Dim iteration As Integer = CInt(100000000 / 100)
        Dim cnt As Long = 0

        For i As Long = 0 To 100000000
            '
            '   Bring the LongRunningProcess to an orderly termination if the Cancel button is clicked.
            '   - see further comments in routine that handles the btnCancelBackgroundProcess_Click event.
            '
            If bw.CancellationPending Then
                Return ""
            End If

            cnt = cnt + 1

            '
            '   Report Progress:
            '   When you need the background operation to report on its progress,
            '   you can call the ReportProgress method to raise the ProgressChanged event.
            '   The ReportProgress Method permits up to 2 parameters (ProgressChangedEventArgs):
            '       Paramater 1. Is of type Integer and is defined as "percentProgress"
            '       Paremeter 2. Is of type Object and is defined as "userState"
            '       I see no reason why you can't return any information you want as long as you conform to the
            '       parameter types integer and object.
            '
            '   In a simple example like this, if the ReportProgress method was called for every iteration of i
            '   then the system would probably hang the user interface and run out of memory. Therefore, in this instance, we
            '   only report back on every 10,000 iterations.
            '
            If (i Mod 10000 = 0) _
                And (bw IsNot Nothing) _
                AndAlso bw.WorkerReportsProgress Then
                '
                '   Call ReportProgress
                '
                bw.ReportProgress(i \ iteration, cnt)
            End If

        Next

        Return cnt.ToString()

    End Function

    Private Sub backgroundWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles bw.DoWork
        '
        ' call long running process and get result
        '
        e.Result = Me.LongRunningProcess()
        '
        ' Cancel if cancel button was clicked.
        '
        If bw.CancellationPending Then
            e.Cancel = True ' This sets the Cancelled property of the RunWorkerCompletedEventArgs
            Return
        End If

    End Sub

    Private Sub backgroundWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bw.ProgressChanged
        '
        '   Use the ProgressChanged Sub to give user feedback via the UI.
        '
        ' Update UI with values contained in ProgressChangedEventArgs e.ProgressPercentage and e.UserState.
        '
        Me.lblPercentCompleted.Content = CStr(e.ProgressPercentage) & "% Completed."
        lblCounter.Content = CStr(e.UserState)
        ''
    End Sub

    Private Sub backgroundWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
        '
        ' Back on primary thread, can access ui controls
        '
        If e.Cancelled Then
            Me.lblStartedCompleted.Content = "Process Cancelled."
        Else
            Me.lblStartedCompleted.Content = "Background Process Completed. "
        End If
        '
        Me.btnStartBackgroundProcess.IsEnabled = True
        Me.btnCancelBackgroundProcess.IsEnabled = False
        ''
    End Sub

    Private Sub btnStartBackgroundProcess_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnStartBackgroundProcess.Click
        '
        lblStartedCompleted.Content = "Background Process Started..."
        Me.btnStartBackgroundProcess.IsEnabled = False
        Me.btnCancelBackgroundProcess.IsEnabled = True
        '
        ' Calls DoWork on secondary thread
        '
        bw.RunWorkerAsync()
        '
        ' RunWorkerAsync returns immediately.
        ''
    End Sub

    Private Sub btnCancelBackgroundProcess_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnCancelBackgroundProcess.Click
        '
        '   There are several points to note when cancelling a process:
        '
        '   1. The WorkerSupportsCancellation property must have been initialised to true.
        '
        '   2. The CancelAsync method does not immediately cancel the process, but instead sets the
        '      CancellationPending property to True.  It is the therefore important that the LongRunningProcess
        '      periodically checks the CancellationPending property and if True the process should be
        '      terminated in an orderly manner.
        '
        '   3. The CancellationPending property of the background worker process should also be checked by the
        '      routine handling the DoWork method after the LongRunningProcess has completed.
        '      If the CancellationPending Property is true then the DoWork routine should set Cancel
        '      property of the DoWorkEventArgs to True.
        '      CancelAsync() method.  This should be checked by the routine which is handling the RunWorkerCompleted
        '      event.
        '
        '   4. It is good practice to ensure theat the Cancel button is only enabled when the LongRunningProcess
        '      is actually running.  Therefore the button should be disabled by default, only enabled when the
        '      process is started, and disable again when the process is completed or cancelled.
        '
        bw.CancelAsync()
        '
        btnCancelBackgroundProcess.IsEnabled = False
        '
    End Sub

End Class

Hope this helps.

About these ads

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Rubric Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: