Browse DevX
Sign up for e-mail newsletters from DevX


Asynchronous Windows Forms Programming : Page 2

Learn how to implement BackgroundWorker in Visual Studio .NET 2003 so you can benefit from its elegant programming model and transition transparently into Windows Forms 2.0 in the future.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Windows Forms 1.1 and Asynchronous Execution (cont.)
Windows Forms base classes make extensive use of ISynchronizeInvoke. The Control class, and every class that derives from Control (such as Form) relies on the underlying Windows messages and a message-processing loop. As explained previously, the message loop must have thread affinity because messages to a window are delivered only to the thread that created it.

ISynchronizeInvoke provides a generic and standard mechanism for marshaling calls between threads.
In general, you must use ISynchronizeInvoke to access a Windows Forms object from another thread, and this is what you should do to support an asynchronous call progress report and completion notification.

Listing 1 demonstrates using ISynchronizeInvoke in a Windows Forms application. The application has a single form called AsyncClient. The form contains a Start button, a Cancel button, a progress bar, and a status label (Figure 1). When the application starts, the Start button is enabled, the Cancel button is disabled, the progress bar shows no progress, and the status label states "Status: Not Started."

Figure 1: The Async Client application.
When you click the Start button it calls the OnStart() method, which asynchronously invokes the DoBackgroundProcessing() method. The asynchronous invocation is dispatched via the private DoWork delegate:

delegate void DoWork(int iterations);

The application calls the BeginInvoke() method of DoWork, passing it the number of iterations to perform (basically the parameter to DoBackgroundProcessing) and a delegate of type AsyncCallback targeting a completion callback method called OnCompleted(). OnStart() also sets the status label to "Status: In Progress," and it disables and enables the Start and Cancel buttons, respectively. The DoBackgroundProcessing() method simulates some lengthy execution by putting the calling thread (the worker thread from the thread pool in this example) to sleep for half a second. DoBackgroundProcessing() does that in a loop for the specified number of iterations it receives as a parameter. To support canceling an asynchronous method call in progress, AsyncClient has a Boolean member variable called m_CancelPending. AsyncClient provides thread-safe access to m_CancelPending via the CancelPending property, which locks the object upon access. You require thread-safe access to m_CancelPending because both the worker thread and the main thread may access it concurrently. Before starting each new iteration, DoBackgroundProcessing() checks the value of the CancelPending property. If it is true, DoBackgroundProcessing() breaks from the loop and returns. After each iteration, DoBackgroundProcessing() reports its current progress by calling the helper method ReportProgress(). The ReportProgress() method should update the progress bar with the current progress. However, DoBackgroundProcessing() calls ReportProgress(), which runs on the worker thread, so it cannot access the progress bar directly. Instead, it obtains from the progress bar its implementation of ISynchronizeInvoke:

ISynchronizeInvoke synchronizer = m_ProgressBar;

ReportProgress() then checks whether the Invoke() method is required. If it is required, ReportProgress() declares a variable of the private delegate type SetInt. SetInt calls the SetProgress() method, which sets the value of the progress bar. ReportProgress() passes the SetInt delegate to the Invoke() method:

SetInt del = new SetInt(SetProgress); try { synchronizer.Invoke(del,new object[]{value}); } catch {}

Invoke() will marshal the call to the main thread where it will be executed by invoking the delegate on the main thread.

As a result, during the execution of DoBackgroundProcessing(), the progress bar reflects the current status properly. When DoBackgroundProcessing() is completed, .NET will automatically call the OnCompleted() method. In OnCompleted() you must call EndInvoke() on the original delegate used to invoke the asynchronous call, even if there are no returned value you care about. You call EndInvoke() to prevent resource leaks because the delegate will keep track of the call it dispatched indefinitely until EndInvoke() is called. You can obtain the original delegate used to invoke the call by down-casting IAsyncResult into AsyncResult and accessing the AsyncDelegate property. In addition, OnCompleted() needs to update the text of the status label. It does so by checking the value of the CancelPending property. If it is true, it sets the status to cancelled, and otherwise to completed. However, OnCompleted() is executed on the worker thread so it cannot access the status label directly. Instead, it uses a similar pattern to the reporting progress and it calls the UpdateStatus() helper method which uses the ISynchronizeInvoke of the status label to update it. For UpdateStatus(), you also need to define a delegate that can invoke a method with a single string parameter, the SetString delegate in Listing 1. All SetString does is set the Text property of the status label.

Advanced developers can somewhat optimize the code in Listing 1 and abstract away the differences between ReportProgress() and UpdateStatus(), but still the net result is a very cumbersome programming model when it comes to incorporating common requirements of asynchronous programming and Windows Forms. Furthermore, the major deficiency of this programming model is the high degree of internal coupling between the various asynchronous execution handling methods, the user interface layout, the controls of the forms, and the required behavior. A small change in any of these will result in major changes to the code in Listing 1.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date