Enhancing Usability of Windows Presentation Foundation (WPF) and Windows Forms Applications
- 2/15/2011
- Before You Begin
- Lesson 1: Implementing Asynchronous Processing
- Lesson 2: Implementing Globalization and Localization
- Lesson 3: Integrating Windows Forms Controls and WPF Controls
- Case Scenarios
- Suggested Practices
- Take a Practice Test
Lesson 1: Implementing Asynchronous Processing
You are frequently required to perform tasks that consume fairly large amounts of time, such as file downloads. The BackgroundWorker component provides an easy way to run time-consuming processes in the background, thereby leaving the user interface (UI) responsive and available for user input.
The BackgroundWorker component is designed to enable you to execute time-consuming operations on a separate, dedicated thread so you can run operations that take a lot of time, such as file downloads and database transactions, asynchronously while the UI remains responsive.
The key method of the BackgroundWorker component is the RunWorkerAsync method. When this method is called, the BackgroundWorker component raises the DoWork event. The code in the DoWork event handler is executed on a separate, dedicated thread so that the UI remains responsive. Table 9-1 shows the important members of the BackgroundWorker component.
Table 9-1 Important Members of the BackgroundWorker Component
Member |
Description |
CancellationPending |
Property. Indicates whether the application has requested cancellation of a background operation. |
IsBusy |
Property. Indicates whether the BackgroundWorker is currently running an asynchronous operation. |
WorkerReportsProgress |
Property. Indicates whether the BackgroundWorker component can report progress updates. |
WorkerSupportsCancellation |
Property. Indicates whether the BackgroundWorker component supports asynchronous cancellation. |
CancelAsync |
Method. Requests cancellation of a pending background operation. |
ReportProgress |
Method. Raises the ProgressChanged event. |
RunWorkerAsync |
Method. Starts the execution of a background operation by raising the DoWork event. |
DoWork |
Event. Occurs when the RunWorkerAsync method is called. Code in the DoWork event handler is run on a separate and dedicated thread. |
ProgressChanged |
Event. Occurs when ReportProgress is called. |
RunWorkerCompleted |
Event. Occurs when the background operation has been completed or cancelled or has raised an exception. |
Running a Background Process
The RunWorkerAsync method of the BackgroundWorker component starts the execution of the background process by raising the DoWork event. The code in the DoWork event handler is executed on a separate thread. The following procedure explains how to create a background process.
To create a background process with the BackgroundWorker component:
From the Toolbox, drag a BackgroundWorker component onto the form.
In the component tray, double-click the BackgroundWorker component to create the default event handler for the DoWork event. Add the code that you want to run on the separate thread. An example is shown here.
Sample of Visual Basic Code
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles BackgroundWorker1.DoWork ' Insert time-consuming operation here End Sub
Sample of C# Code
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // Insert time-consuming operation here }
Elsewhere in your code, start the time-consuming operation on a separate thread by calling the RunWorkerAsync method, as shown:
Sample of Visual Basic Code
BackgroundWorker1.RunWorkerAsync()
Sample of C# Code
backgroundWorker1.RunWorkerAsync();
Providing Parameters to the Background Process
Sometimes you will want to run a background process that requires a parameter. For example, you might want to provide the address of a file for download. You can provide a parameter in the RunWorkerAsync method. This parameter will be available as the Argument property of the instance of DoWorkEventArgs in the DoWork event handler.
To provide a parameter to a background process:
Include the parameter in the RunWorkerAsync call, as shown here:
Sample of Visual Basic Code
BackgroundWorker1.RunWorkerAsync("C:\myfile.txt")
Sample of C# Code
backgroundWorker1.RunWorkerAsync("C:\\myfile.txt");
Retrieve the parameter from the DoWorkEventArgs.Argument property and cast it appropriately to use it in the background process. An example is shown here:
Sample of Visual Basic Code
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles BackgroundWorker1.DoWork Dim myPath As String myPath = CType(e.Argument, String) ' Use the argument in the process RunTimeConsumingProcess() End Sub
Sample of C# Code
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { string myPath; myPath = (string)e.Argument; // Use the argument in the process RunTimeConsumingProcess(); }
Announcing the Completion of a Background Process
When the background process terminates, whether because the process is completed or cancelled, the RunWorkerCompleted event is raised. You can alert the user to the completion of a background process by handling the RunWorkerCompleted event. Here is an example:
Sample of Visual Basic Code
Private Sub BackgroundWorker1_RunWorkerCompleted( _ ByVal sender As System.Object, _ ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _ Handles BackgroundWorker1.RunWorkerCompleted MsgBox("Background process completed!") End Sub
Sample of C# Code
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { System.Windows.Forms.MessageBox.Show("Background process completed"); }
You can ascertain whether the background process was cancelled by reading the e.Cancelled property, as shown here:
Sample of Visual Basic Code
Private Sub BackgroundWorker1_RunWorkerCompleted( _ ByVal sender As System.Object, _ ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _ Handles BackgroundWorker1.RunWorkerCompleted If e.Cancelled Then MsgBox("Process was cancelled!") Else MsgBox("Process completed") End If End Sub
Sample of C# Code
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled) { System.Windows.Forms.MessageBox.Show ("Process was cancelled!"); } else { System.Windows.Forms.MessageBox.Show("Process completed"); } }
Returning a Value from a Background Process
You might want to return a value from a background process. For example, if your process is a complex calculation, you would want to return the result. You can return a value by setting the Result property of DoWorkEventArgs in DoWorkEventHandler. This value will then be available in the RunWorkerCompleted event handler as the Result property of the RunWorkerCompletedEventArgs parameter, as shown in the following example:
Sample of Visual Basic Code
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles BackgroundWorker1.DoWork ' Assigns the return value of a method named ComplexCalculation to ' e.Result e.Result = ComplexCalculation() End Sub Private Sub BackgroundWorker1_RunWorkerCompleted( _ ByVal sender As System.Object, _ ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _ Handles BackgroundWorker1.RunWorkerCompleted MsgBox("The result is " & e.Result.ToString) End Sub
Sample of C# Code
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // Assigns the return value of a method named ComplexCalculation to // e.Result e.Result = ComplexCalculation(); } private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { System.Windows.Forms.MessageBox.Show("The result is " + e.Result.ToString()); }
Cancelling a Background Process
You might want to implement the ability to cancel a background process. BackgroundWorker supports this ability, but you must implement most of the cancellation code yourself. The WorkerSupportsCancellation property of the BackgroundWorker component indicates whether the component supports cancellation. You can call the CancelAsync method to attempt to cancel the operation; doing so sets the CancellationPending property of the BackgroundWorker component to True. By polling the CancellationPending property of the BackgroundWorker component, you can determine whether to cancel the operation.
To implement cancellation for a background process:
In the Properties window, set the WorkerSupportsCancellation property to True to enable the BackgroundWorker component to support cancellation.
Create a method that is called to cancel the background operation. The following example demonstrates how to cancel a background operation in a Button.Click event handler:
Sample of Visual Basic Code
Private Sub btnCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCancel.Click BackgroundWorker1.CancelAsync() End Sub
Sample of C# Code
private void btnCancel_Click(object sender, EventArgs e) { backgroundWorker1.CancelAsync(); }
In the BackgroundWorker.DoWork event handler, poll the BackgroundWorker.CancellationPending property and implement code to cancel the operation if it is True. You should also set the e.Cancel property to True, as shown in the following example:
Sample of Visual Basic Code
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles BackgroundWorker1.DoWork For i As Integer = 1 to 1000000 TimeConsumingMethod() If BackgroundWorker1.CancellationPending Then e.Cancel = True Exit Sub End If Next End Sub
Sample of C# Code
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { for (int i = 0; i < 1000000; i++) { TimeConsumingMethod(); if (backgroundWorker1.CancellationPending) { e.Cancel = true; return; } } }
Reporting Progress of a Background Process with BackgroundWorker
For particularly time-consuming operations, you might want to report progress back to the primary thread. You can report progress of the background process by calling the ReportProgress method. This method raises the BackgroundWorker.ProgressChanged event and enables you to pass a parameter that indicates the percentage of progress that has been completed to the methods that handle that event. The following example demonstrates how to call the ReportProgress method from within the BackgroundWorker.DoWork event handler and then how to update a ProgressBar control in the BackgroundWorker.ProgressChanged event handler:
Sample of Visual Basic Code
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles BackgroundWorker1.DoWork For i As Integer = 1 to 10 RunTimeConsumingProcess() ' Calls the Report Progress method, indicating the percentage ' complete BackgroundWorker1.ReportProgress(i*10) Next End Sub Private Sub BackgroundWorker1_ProgressChanged( _ ByVal sender As System.Object, _ ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles BackgroundWorker1.ProgressChanged ProgressBar1.Value = e.ProgressPercentage End Sub
Sample of C# Code
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { for (int i = 1;i < 11; i++) { RunTimeConsumingProcess(); // Calls the Report Progress method, indicating the percentage // complete backgroundWorker1.ReportProgress(i*10); } } private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { progressBar1.Value = e.ProgressPercentage; }
Note that to report progress with the BackgroundWorker component, you must set the WorkerReportsProgress property to True.
Requesting the Status of a Background Process
You can determine whether a BackgroundWorker component is executing a background process by reading the IsBusy property, which returns a Boolean value. If True, the BackgroundWorker component is currently running a background process. If False, the BackgroundWorker component is idle. An example follows:
Sample of Visual Basic Code
If Not BackgroundWorker1.IsBusy BackgroundWorker1.RunWorkerAsync() End If
Sample of C# Code
if (!(backgroundWorker1.IsBusy)) { backgroundWorker1.RunWorkerAsync(); }
Using Delegates
Special classes called delegates enable you to call methods in a variety of ways. A delegate is essentially a type-safe function pointer that enables you to pass a reference to an entry point for a method and invoke that method in a variety of ways without making an explicit function call. You use the Delegate keyword (delegate in C#) to declare a delegate, and you must specify the same method signature as the method that you want to call with the delegate. The following example demonstrates a sample method and the declaration of a delegate that can be used to call that method:
Sample of Visual Basic Code
Public Function TestMethod(ByVal I As Integer) As String ' Insert method implementation here End Function Public Delegate Function myDelegate(ByVal I As Integer) As String
Sample of C# Code
public string TestMethod(int I) { // Insert method implementation here } public delegate string myDelegate(int i);
After a delegate has been declared, you can create an instance of it that specifies a method that has the same signature. In C#, you can specify the method by simply naming the method. In Visual Basic, you must use the AddressOf operator to specify the method. The following example demonstrates how to create an instance of the delegate that specifies the method shown in the previous example.
Sample of Visual Basic Code
Dim del As New myDelegate(AddressOf TestMethod)
Sample of C# Code
myDelegate del = new myDelegate(TestMethod);
After an instance of a delegate has been created, you can invoke the method that refers to the delegate by simply calling the delegate with the appropriate parameters or by using the delegate’s Invoke method. Both are shown in the following example:
Sample of Visual Basic Code
del(342) del.Invoke(342)
Sample of C# Code
del(342); del.Invoke(342);
Using Delegates Asynchronously
Delegates can be used to call any method asynchronously. In addition to the Invoke method, every delegate exposes two methods, BeginInvoke and EndInvoke, that call methods asynchronously. Calling the BeginInvoke method on a delegate starts the method that it refers to on a separate thread. Calling EndInvoke retrieves the results of that method and ends the separate thread.
The BeginInvoke method begins the asynchronous call to the method represented by the delegate. It requires the same parameters as the method the delegate represents, as well as two additional parameters: an AsyncCallback delegate that references the method to be called when the asynchronous method is completed, and a user-defined object that contains information about the asynchronous call. BeginInvoke returns an instance of IAsyncResult, which monitors the asynchronous call.
The EndInvoke method retrieves the results of the asynchronous call and can be called any time after BeginInvoke has been called. The EndInvoke method signature requires as a parameter the instance of IAsyncResult returned by BeginInvoke and returns the value that is returned by the method represented by the delegate. The method signature also contains any Out or ByRef parameters of the method it refers to in its signature.
You can use BeginInvoke and EndInvoke in several ways to implement asynchronous methods. Among them are the following:
Calling BeginInvoke, doing work, and then calling EndInvoke on the same thread
Calling BeginInvoke, polling IAsyncResult until the asynchronous operation is completed, and then calling EndInvoke
Calling BeginInvoke, specifying a callback method to be executed when the asynchronous operation has completed, and calling EndInvoke on a separate thread
Waiting for an Asynchronous Call to Return with EndInvoke
The simplest way to implement an asynchronous method call is to call BeginInvoke, do some work, and then call EndInvoke on the same thread that BeginInvoke was called on. Although this approach is simplest, a potential disadvantage is that the EndInvoke call blocks execution of the thread until the asynchronous operation is completed if it has not completed yet. Thus, your main thread might still be unresponsive if the asynchronous operation is particularly time-consuming. The –DelegateCallback and –AsyncState parameters are not required for this operation, so Nothing (null in C#) can be supplied for these parameters. The following example demonstrates how to implement an asynchronous call in this way, using the TestMethod and myDelegate methods that were defined in the preceding examples:
Sample of Visual Basic Code
Dim del As New myDelegate(AddressOf TestMethod) Dim result As IAsyncResult result = del.BeginInvoke(342, Nothing, Nothing) ' Do some work while the asynchronous operation runs Dim ResultString As String ResultString = del.EndInvoke(result)
Sample of C# Code
myDelegate del = new myDelegate(TestMethod); IAsyncResult result; result = del.BeginInvoke(342, null, null); // Do some work while the asynchronous operation runs string ResultString; ResultString = del.EndInvoke(result);
Polling IAsyncResult until Completion
Another way of executing an asynchronous operation is to call BeginInvoke and then poll the IsCompleted property of IAsyncResult to determine whether the operation has finished. When the operation has finished, you can then call EndInvoke. An advantage of this approach is that you do not need to call EndInvoke until the operation is complete. Thus, you do not lose any time by blocking your main thread. The following example demonstrates how to poll the IsCompleted property:
Sample of Visual Basic Code
Dim del As New myDelegate(AddressOf TestMethod) Dim result As IAsyncResult result = del.BeginInvoke(342, Nothing, Nothing) While Not result.IsCompleted ' Do some work End While Dim ResultString As String ResultString = del.EndInvoke(result)
Sample of C# Code
myDelegate del = new myDelegate(TestMethod); IAsyncResult result; result = del.BeginInvoke(342, null, null); while (!(result.IsCompleted)) { // Do some work while the asynchronous operation runs } string ResultString; ResultString = del.EndInvoke(result);
Executing a Callback Method When the Asynchronous Operation Returns
If you do not need to process the results of the asynchronous operation on the same thread that started the operation, you can specify a callback method to be executed when the operation is completed. This enables the operation to complete without interrupting the thread that initiated it. To execute a callback method, you must provide an instance of AsyncCallback that specifies the callback method. You can also supply a reference to the delegate itself so that EndInvoke can be called in the callback method to complete the operation. The following example demonstrates how to specify and run a callback method:
Sample of Visual Basic Code
Private Sub CallAsync() Dim del As New myDelegate(AddressOf TestMethod) Dim result As IAsyncResult Dim callback As New AsyncCallback(AddressOf CallbackMethod) result = del.BeginInvoke(342, callback, del) End Sub Private Sub CallbackMethod(ByVal result As IAsyncResult) Dim del As myDelegate Dim ResultString As String del = CType(result.AsyncState, myDelegate) ResultString = del.EndInvoke(result) End Sub
Sample of C# Code
private void CallAsync() { myDelegate del = new myDelegate(TestMethod); IAsyncResult result; AsyncCallback callback = new AsyncCallback(CallbackMethod); result = del.BeginInvoke(342, callback, del); } private void CallbackMethod(IAsyncResult result) { myDelegate del; string ResultString; del = (myDelegate)result.AsyncState; ResultString = del.EndInvoke(result); }
Creating Process Threads
For applications that require more precise control over multiple threads, you can create new threads with the Thread object, which represents a separate thread of execution that runs concurrently with other threads. You can create as many Thread objects as you like, but the more threads there are, the greater the impact on performance and the greater the possibility of adverse threading conditions, such as deadlocks.
Creating and Starting a New Thread
The Thread object requires a delegate to the method that will serve as the starting point for the thread. This method must be a Sub (void in C#) method and must either have no parameters or take a single –Object parameter. In the latter case, the –Object parameter passes any required parameters to the method that starts the thread. After a thread is created, you can start it by calling the Thread.Start method. The following example demonstrates how to create and start a new thread:
Sample of Visual Basic Code
Dim aThread As New System.Threading.Thread(Addressof aMethod) aThread.Start()
Sample of C# Code
System.Threading.Thread aThread = new System.Threading.Thread(aMethod); aThread.Start();
For threads that accept a parameter, the procedure is similar except that the starting method can take a single Object as a parameter, and that object must be specified as the parameter in the Thread.Start method. Here is an example:
Sample of Visual Basic Code
Dim aThread As New System.Threading.Thread(Addressof aMethod) aThread.Start(anObject)
Sample of C# Code
System.Threading.Thread aThread = new System.Threading.Thread(aMethod); aThread.Start(anObject);
Destroying Threads
You can destroy a Thread object by calling the Thread.Abort method. This method causes the thread on which it is called to cease its current operation and to raise a ThreadAbortException exception. If a Catch block is capable of handling the exception, it will execute along with any Finally blocks. The thread is then destroyed and cannot be restarted.
Sample of Visual Basic Code
aThread.Abort()
Sample of C# Code
aThread.Abort();
Synchronizing Threads
Two of the most common difficulties involved in multithread programming are deadlocks and race conditions. A deadlock occurs when one thread has exclusive access to a particular variable and then attempts to gain exclusive access to a second variable at the same time that a second thread has exclusive access to the second variable and attempts to gain exclusive access to the variable locked by the first thread. The result is that both threads wait indefinitely for the other to release the variables, and they cease operating.
A race condition occurs when two threads attempt to access the same variable at the same time. For example, consider two threads that access the same collection. The first thread might add an object to the collection. The second thread might then remove an object from the collection based on the index of the object. The first thread might then attempt to access the object in the collection to find that it had been removed. Race conditions can lead to unpredictable effects that can destabilize your application.
The best way to avoid race conditions and deadlocks is by careful programming and judicious use of thread synchronization. You can use the SyncLock keyword in Visual Basic and the lock keyword in C# to obtain an exclusive lock on an object. This enables the thread that has the lock on the object to perform operations on that object without allowing any other threads to access it. Note that if any other threads attempt to access a locked object, those threads will pause until the lock is released. The following example demonstrates how to obtain a lock on an object:
Sample of Visual Basic Code
SyncLock anObject ' Perform some operation End SyncLock
Sample of C# Code
lock (anObject) { // Perform some operation }
Some objects, such as collections, implement a synchronization object that should be used to synchronize access to the greater object. The following example demonstrates how to obtain a lock on the SyncRoot object of an ArrayList object:
Sample of Visual Basic Code
Dim anArrayList As New System.Collections.ArrayList SyncLock anArrayList.SyncRoot ' Perform some operation on the ArrayList End SyncLock
Sample of C# Code
System.Collections.Arraylist anArrayList = new System.Collections.ArrayList(); lock (anArrayList.SyncRoot) { // Perform some operation on the ArrayList }
It is generally good practice when creating classes that will be accessed by multiple threads to include a synchronization object for synchronized access by threads. This enables the system to lock only the synchronization object, thus conserving resources by not having to lock every single object contained in the class. A synchronization object is simply an instance of Object, and does not need to have any functionality except to be available for locking. The following example demonstrates a class that exposes a synchronization object:
Sample of Visual Basic Code
Public Class aClass Public SynchronizationObject As New Object() ' Insert additional functionality here End Class
Sample of C# Code
public class aClass { public object SynchronizationObject = new Object(); // Insert additional functionality here }
Special Considerations when Working with Controls
Because controls are always owned by the UI thread, it is generally unsafe to make calls to controls from a different thread. In WPF applications, you can use the Dispatcher object, discussed later in this lesson, to make safe function calls to the UI thread. In Windows Forms applications, you can use the Control.InvokeRequired property to determine whether it is safe to make a call to a control from another thread. If InvokeRequired returns False, it is safe to make the call to the control. If InvokeRequired returns True, however, you should use the Control.Invoke method on the owning form to supply a delegate to a method to access the control. Using Control.Invoke enables the control to be accessed in a thread-safe manner. The following example demonstrates setting the Text property of a TextBox control named Text1:
Sample of Visual Basic Code
Public Delegate Sub SetTextDelegate(ByVal t As String) Public Sub SetText(ByVal t As String) If TextBox1.InvokeRequired = True Then Dim del As New SetTextDelegate(AddressOf SetText) Me.Invoke(del, New Object() {t}) Else TextBox1.Text = t End If End Sub
Sample of C# Code
public delegate void SetTextDelegate(string t); public void SetText(string t) { if (textBox1.InvokeRequired) { SetTextDelegate del = new SetTextDelegate(SetText); this.Invoke(del, new object[]{t}); } else { textBox1.Text = t; } }
In the preceding example, the method tests InvokeRequired to determine whether it is dangerous to access the control directly. In general, this will return True if the control is being accessed from a separate thread. If InvokeRequired does return True, the method creates a new instance of a delegate that refers to itself and calls Control.Invoke to set the Text property in a thread-safe manner.
Using Dispatcher to Access Controls Safely on Another Thread in WPF
At times, you might want to change the user interface from a worker thread. For example, you might want to enable or disable buttons based on the status of the worker thread, or to provide more detailed progress reporting than is allowed by the ReportProgess method. The WPF threading model provides the Dispatcher class for cross-thread calls. Using Dispatcher, you can update your user interface safely from worker threads.
You can retrieve a reference to the Dispatcher object for a UI element from its Dispatcher property, as shown here:
Sample of Visual Basic Code
Dim aDisp As System.Windows.Threading.Dispatcher aDisp = Button1.Dispatcher
Sample of C# Code
System.Windows.Threading.Dispatcher aDisp; aDisp = button1.Dispatcher;
Dispatcher provides two principal methods you will use: BeginInvoke and Invoke. Both methods enable you to call a method safely on the UI thread. The BeginInvoke method enables you to call a method asynchronously, and the Invoke method enables you to call a method synchronously. Thus, a call to Dispatcher.Invoke will block execution on the thread on which it is called until the method returns, whereas a call to Dispatcher.BeginInvoke will not block execution.
Both the BeginInvoke and Invoke methods require you to specify a delegate that points to a method to be executed. You can also supply a single parameter or an array of parameters for the delegate, depending on the requirements of the delegate. You also are required to set the DispatcherPriority property, which determines the priority with which the delegate is executed. In addition, the Dispatcher.Invoke method enables you to set a period of time for the Dispatcher to wait before abandoning the invocation. The following example demonstrates how to invoke a delegate named MyMethod, using BeginInvoke and Invoke:
Sample of Visual Basic Code
Dim aDisp As System.Windows.Threading.Dispatcher = Button1.Dispatcher ' Invokes the delegate synchronously aDisp.Invoke(System.Windows.Threading.DispatcherPriority.Normal, MyMethod) ' Invokes the delegate asynchronously aDisp.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, MyMethod)
Sample of C# Code
System.Windows.Threading.Dispatcher aDisp = button1.Dispatcher; // Invokes the delegate synchronously aDisp.Invoke(System.Windows.Threading.DispatcherPriority.Normal, MyMethod); // Invokes the delegate asynchronously aDisp.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, MyMethod);
Practice: Working with BackgroundWorker
In this practice, you use the BackgroundWorker component. You write a time-consuming method to be executed on a separate thread. You implement cancellation functionality, and you use Dispatcher to update the user interface from the worker thread.
EXERCISE Practice with BackgroundWorker
Open the partial solution for this exercise from its location on the companion CD. The partial solution already has a user interface built and has code for a BackgroundWorker component and stubs for methods. Note that the WorkerSupportsCancellation property of BackgroundWorker is set to True in the constructor in the partial solution.
In Code view, add the following code to the Window1 class for a delegate to the UpdateLabel method:
Sample of Visual Basic Code
Private Delegate Sub UpdateDelegate(ByVal i As Integer) Private Sub UpdateLabel(ByVal i As Integer) Label1.Content = "Cycles: " & i.ToString End Sub
Sample of C# Code
private delegate void UpdateDelegate(int i); private void UpdateLabel(int i) { Label1.Content = "Cycles: " + i.ToString(); }
In the DoWork event handler, add the following code:
Sample of Visual Basic Code
For i As Integer = 0 To 500 For j As Integer = 1 To 10000000 Next If aWorker.CancellationPending Then e.Cancel = True Exit For End If Dim update As New UpdateDelegate(AddressOf UpdateLabel) Label1.Dispatcher.BeginInvoke(update, Windows.Threading. _ DispatcherPriority.Normal, i) Next
Sample of C# Code
for (int i = 0; i <= 500; i++) { for (int j = 1; j <= 10000000; j++) { } if (aWorker.CancellationPending) { e.Cancel = true; return; } UpdateDelegate update = new UpdateDelegate(UpdateLabel); Label1.Dispatcher.BeginInvoke( System.Windows.Threading.DispatcherPriority.Normal, update, i); }
In Code view, add the following code to the RunWorkerCompleted event handler:
Sample of Visual Basic Code
If Not e.Cancelled Then Label2.Content = "Run Completed" Else Label2.Content = "Run Cancelled" End If
Sample of C# Code
if (!(e.Cancelled)) Label2.Content = "Run Completed"; else Label2.Content = "Run Cancelled";
In the designer, double-click the button marked Start to open the Button1_Click event handler and add the following code:
Sample of Visual Basic Code
Label2.Content = "" aWorker.RunWorkerAsync()
Sample of C# Code
label2.Content = ""; aWorker.RunWorkerAsync();
In the designer, double-click the Cancel button to open the Button2_Click event handler and add the following code:
Sample of Visual Basic Code
aWorker.CancelAsync()
Sample of C# Code
aWorker.CancelAsync();
Press F5 to run your application and test the functionality.
Lesson Summary
The BackgroundWorker component enables you to execute operations on a separate thread of execution. You call the RunWorkerAsync method of the BackgroundWorker component to begin the background process. The event handler for the DoWork method contains the code that will execute on a separate thread.
The BackgroundWorker.RunCompleted event is fired when the background process is completed.
You can enable cancellation of a background process by setting the BackgroundWorker.WorkerSupportsCancellation property to True. You then signal BackgroundWorker to cancel the process by calling the CancelAsync method, which sets the CancellationPending method to True. You must poll the CancellationPending property and implement cancellation code if the CancellationPending property registers as True.
You can report progress from the background operation. First you must set the WorkerReportsProgress property to True. You can then call the ReportProgress method from within the background process to report progress. This raises the ProgressChanged event, which you can handle to take any action.
A control’s Dispatcher object can be used to execute code safely in the user interface from a worker thread. Dispatcher.BeginInvoke is used to execute code asynchronously, and Dispatcher.Invoke is used to execute code synchronously.
Delegates are type-safe function pointers that enable you to call methods with the same signature. You can call methods synchronously by using the delegate’s Invoke method, or asynchronously by using BeginInvoke and EndInvoke.
When BeginInvoke is called, an operation specified by the delegate is started on a separate thread. You can retrieve the result of the operation by calling EndInvoke, which will block the calling thread until the background process is completed. You can also specify a callback method to complete the operation on the background thread if the main thread does not need the result.
Thread objects represent separate threads of operation and provide a high degree of control of background processes. You can create a new thread by specifying a method that serves as an entry point for the thread.
You can use the SyncLock (Visual Basic) and lock (C#) keywords to restrict access to a resource to a single thread of execution.
You must not make calls to controls from background threads. Use the Control.InvokeRequired property to determine whether it is safe to make a direct call to a control. If it is not safe to make a direct call to the control, use the Control.Invoke method to make a safe call to the control.
Lesson Review
You can use the following questions to test your knowledge of the information in Lesson 1: Implementing Asynchronous Processing. The questions are also available on the companion CD if you prefer to review them in electronic form.
Which of the following are required to start a background process with the BackgroundWorker component? (Choose all that apply.)
Calling the RunWorkerAsync method
Handling the DoWork event
Handling the ProgressChanged event
Setting the WorkerSupportsCancellation property to True
Which of the following are good strategies for updating the user interface from the worker thread? (Choose all that apply.)
Use Dispatcher.BeginInvoke to execute a delegate to a method that updates the user interface.
Invoke a delegate to a method that updates the user interface.
Set the WorkerReportsProgress property to True, call the ReportProgress method in the background thread, and handle the ProgressChanged event in the main thread.
Call a method that updates the user interface from the background thread.