Export (0) Print
Expand All
10 out of 489 rated this helpful - Rate this topic

Microsoft .NET Compact Framework Background Processing Techniques

.NET Compact Framework 1.0
 

Jim Wilson
JW Hedgehog, Inc.

March 2003

Applies to:
    Microsoft® .NET Compact Framework
    Microsoft® Visual Studio® .NET 2003

Summary: Performing background processing has many implications and requires very careful design. This paper offers some suggestions for making the best use of background processing and introduces many of the issues that must be addressed. (9 printed pages)

Contents

Introduction
Types of Background Processing
Background Processing and the User Experience
Conclusion

Introduction

Applications are often faced with the need to perform potentially long-running operations while still needing to provide a positive and responsive user experience. Nowhere is this truer then in smart device development. An application running on a Pocket PC can never prevent the user from performing necessary tasks, but at the same time the limited CPU power and low-bandwidth connectivity so common on these devices limits the speed with which many process can be performed (exacerbated by ever more complex application requirements). Often the best way to handle long-running tasks is to do the long-running processing in the background allowing the user to still interact with the application.

Performing background processing has many implications and requires very careful design. The following offers some suggestions for making the best use of background processing and introduces many of the issues that must be addressed.

Types of Background Processing

The .NET Compact Framework provides three primary techniques for moving long-running processing into the background. Each technique, asynchronous XML Web services, a thread pool and explicitly created threads, is designed for a particular usage scenario and requires proper follow-up and management.

Asynchronous XML Web Services

Web services provide an easy to use mechanism for communicating between smart devices and central services. Very often Web services must communicate large amounts of data and must do so over a relatively low-speed mobile connection. This kind of long-running data transfer is a perfect opportunity to take advantage of the .NET Compact Framework’s ability to manage Web service calls asynchronously.

Normally Web services are called through a proxy class generated either by Visual Studio .NET’s “Add Web Reference” or by the WSDL command line utility. This technique allows the application developer to call a local method on the proxy class that takes care of the details of generating, transmitting and parsing the XML required to actually call the remote Web service. The generated proxy class provides three methods for each Web service operation. One method named the same as the Web service operation which calls the Web service synchronously and a pair of methods prefixed with “Begin” and “End” to call the Web service asynchronously. For example the proxy for a Web service exposing an Add operation will contain methods named Add, BeginAdd and EndAdd.

Note   For more information on using Visual Studio .NET’s “Add Web Reference” feature see Adding and Removing Web References. For more information on the WSDL command line utility see Web Services Description Language Tool.

Making an Asynchronous Web Service Call

To launch a Web service asynchronously is as simple as calling the “Begin” method for the operation. Assuming the Web service Add operation expects two integer parameters; the following code calls Web service asynchronously.

MathProxy wsMath = new MathProxy();
wsMath.BeginAdd(10, 15, null, null);

The call to BeginAdd does not directly call the Web service but instead queues the actual Web service call then returns immediately. No information from the Web service itself is available when the call returns. Instead an IAsyncResult reference is returned which is used to retrieve the results of the real Web service call.

class MyCalcForm : Form
{
   private IAsyncResult _addAsyncResult ;
   private MathProxy _wsMath = new MathProxy() ;
   public void btnAdd_OnClick(object sender, EventArgs e)
   {
      _addAsyncResult = _wsMath.BeginAdd(10, 15, null, null) ;
   }

The IAsyncResult enables the application to determine when the call completes and is also needed to retrieve the call results. Storing the IAsyncResult as a class member allows the application to retrieve the results of the Web service call in a different method then initiated the call.

To retrieve the results of the Web service, the application calls the proxy “End” method passing the previously returned IAsyncResult as shown here.

int sum = _wsMath.EndAdd(_addAsyncResult) ;

The “End” method returns the Web service return value as well as any ref (ByRef) or out parameters.

Determining when an Asynchronous Web Service Call Completes

Before retrieving the results of an Asynchronous Web service call, the application must first determine that the call has completed. An application can choose to be notified of completion, block waiting for completion or can poll to determine if the Web service has completed.

Notification

In most cases, notification is the best choice as it allows the application to initiate the call to the Web service and then proceed without any other special handling. When the Web service call is initiated, the application passes an AsyncCallback delegate to the “Begin” method. The delegate will automatically be called when the Web service call completes. When using notification, it is not necessary to store the IAsyncResult returned by the “Begin” methods as it is automatically passed as a parameter to the notification delegate.

class MyCalcForm : Form
{
   private MathProxy _wsMath = new MathProxy();
   public int _sum;
   public void btnAdd_OnClick(object sender, EventArgs e)
   {
      AsyncCallback cb = new AsyncCallback(OnAddComplete);
      _wsMath.BeginAdd(10, 15, cb, null);  // initiate XML web service call
   }
   public OnAddComplete(IAsyncResult ar)  // called by framework when done
   {
      _sum = _wsMath.EndAdd(ar);           // get XML web service result
   }

Caution   The method called by the delegate runs on a background thread, not the main application thread. As a result the delegate method cannot safely affect the user interface. See the “Background Processing and the User Experience” section later in this document for details.

Blocking

In some cases an application may be able to do only a portion of its work until the Web service results are returned. In this case it is useful to initiate the Web service, do the portion of the work that does not depend on the Web service results and then block until the Web service Completes. The IAsyncResult.AsyncWaitHandle provides this capability. When the Web service call is initiated the AsyncWaitHandle is in a non-signaled state, When the Web service call completes, the AsyncWaitHandle is signaled. Using the WaitOne method on the AsyncWaitHandle will cause the calling thread (normally the main application thread) to block until the Web service completes.

public void DoCalculations()
{
   IAsyncResult ar = _wsMath.BeginAdd(10, 15, null, null);
   // do work that doesn’t need Add results
   ar.AsyncWaitHandle.WaitOne() ; // blocks until call completes
   int sum = _wsMath.EndAdd(ar) ; // retrieve results
   // continue processing
}

Polling

In some rare occasions, an application may wish to continue processing while periodically checking on the Web service call. Using IAsyncResult.IsCompleted, the application can determine if the Web service call has completed.

   IAsyncResult ar = _wsMath.BeginAdd(10, 15, null, null);
   While (! ar.IsCompleted)  // loop until call completes
   {
      // do some other work while waiting 
   }
   int sum = _wsMath.EndAdd(ar) ;  // retrieve results

Thread Pool

In cases where an application needs to perform a long-running local process such as a lengthy calculation, file processing or initialization the .NET Compact Framework provides a built in thread pool. The thread pool allows jobs to be run in the background without the overhead of frequently creating and destroying separate threads for each task.

The Compact Framework thread pool is exposed through the ThreadPool class. Tasks are queued into the thread pool by wrapping a method in a WaitCallback delegate and passing the delegate to the ThreadPool.QueueUserWorkItem static method.

   void ReadBigFile(object val) 
   {
      // Do work to read and process a file
   }

   public void btnStartRead_Click(object sender, EventArgs e)
   {
      // wrap ReadBigFile in the delegate and submit to the Thread Pool
      WaitCallback w = new WaitCallback(ReadBigFile) ;
      ThreadPool.QueueUserWorkItem(w) ; 
   }

In the above example the ReadBigFile method will be queued into the thread pool and run on one of the available threads. The call to QueueUserWorkItem blocks just long enough to place the request in the queue.

In cases where the background process needs to be passed a parameter, QueueUserWorkItem provides an overload that takes an additional parameter which is passed to the queued method. The parameter is defined as an object so any parameter type can be passed.

   void ReadBigFile2(object val) 
   {
      string dataFile = (string) val ; // val is a reference to fName
      // Do work to read and process dataFile
   }

   public void btnStartRead_Click(object sender, EventArgs e)
   {
      string fName = “BigDataFile.xml” ;
      WaitCallback w = new WaitCallback(ReadBigFile2) ;
      // fName will be passed to ReadBigFile
      ThreadPool.QueueUserWorkItem(w, fName) ;  
   }

Explicitly Created Threads

In some cases, a background process may run for much of the life of the application such as continually reading a Global Positioning System or monitoring a piece of equipment. In these cases, it’s best to create a thread for the specific task.

Dedicated threads are exposed through instances of the Thread class and are created by wrapping a method in a ThreadStart delegate, passing the delegate to the Thread class constructor and then calling the Thread.Start method.

   void ReadGPSFeed() 
   {
      // Loop reading GPS data
   }

   public void btnStartGPS_Click(object sender, EventArgs e)
   {
      ThreadStart startMethod = new ThreadStart(ReadGPSFeed);
      Thread gpsThread = new Thread(startMethod);
      gpsThread.Start();        // start background thread
   }

In this example, the ReadGPSFeed method is run on a newly created thread. The thread is dedicated to the ReadGPSFeed method and will terminate once the method exits.

The nature of work done on a dedicated thread is normally to perform the processing in some kind of loop. Although in some cases, the background process itself may make the determination that the processing is completed; more commonly the determination is made by some external event such as a user request or application shutdown. Signaling the background thread to terminate is most easily achieved through a class-level Boolean flag as shown in the following.

Class MyForm : Form
{
   // Some class members elided for clarity
   private bool _continueGPSRead = false;  // Read control flag
   void ReadGPSFeed() 
   {
      while (_continueGPSRead) // loop until false
      {
        // Read GPS data
      }
   }
   public void btnStartGPS_Click(object sender, EventArgs e)
   {
      ThreadStart startMethod = new ThreadStart(ReadGPSFeed);
      Thread gpsThread = new Thread(startMethod);
      _continueGPSRead = true;  // set loop flag
      gpsThread.Start();        // start background thread
   }
   public void btnStopGPS_Click(object sender, EventArgs e)
   {    
      _continueGPSRead = false ; // Signal ReadGPSFeed to terminate
   }
}

In this example, btnStartGPS_Click initiates the background processing by setting the flag to true and starting the associated thread. The ReadGPSFeed method will continue processing until the _continueGPSRead flag is set to false by btnStopGPS_Click.

Background Processing and the User Experience

Background processing is a powerful tool and used correctly can substantially improve the user experience. However used incorrectly, it can just as easily create confusion and even possibly errors. The following are a few items to keep in mind to insure a positive user experience.

Do Not Directly Affect the User Interface

An application initially starts with a single thread. Normally all user interface controls are created by this thread. Windows CE user interface objects have Thread Affinity, meaning that they are closely coupled with the thread on which they are created. This close coupling is due to the fact that all interaction with Windows CE user interface objects (both reading and updating) relies on message queues that are managed by the creating thread. Interacting with the interface object’s message queue from a thread other then the creating thread may cause data corruption or other errors. The Windows Forms controls in the .NET Compact Framework are lightweight wrappers over the Windows CE user interface objects and are subject to the same restrictions. This restriction applies both to the thread pool and explicitly created threads.

To allow background processes to safely interact with the user interface, all Windows Forms controls provide a special Invoke method. Calling the Invoke method transfers control to thread that the Windows Form control was created on and executes a delegate on that thread. The method to be run on the user interface thread must be wrapped in an event handler delegate.

class MyForm : Form
{
   // some members elided for clarity
   private MathProxy _wsMath = new MathProxy();
   protected TextBox _txtSum; // will display XML web service results
   protected int _sum ;
   public void btnAdd_OnClick(object sender, EventArgs e)
   {
      AsyncCallback cb = new AsyncCallback(OnAddComplete);
      _wsMath.BeginAdd(10, 15, cb, null);  // initiate XML web service call
   }
   public OnAddComplete(IAsyncResult ar)  // called by framework when done
   {
      _sum = _wsMath.EndAdd(ar);        // get XML web service result
      // execute UpdateSumDisplay on the thread where _txtSum was created
      _txtSum.Invoke(new EventHandler(UpdateSumDisplay));
   }
   public void UpdateSumDisplay(object sender, EventArgs e)
   {
      _txtSum.Text = _sum.ToString();
   }

In this example, btnAdd_OnClick makes an asynchronous Web service call. The OnAddComplete method is automatically called when the asynchronous call completes. As discussed in the “Asynchronous Web services” section, the OnAddComplete method runs on a background thread and as a result cannot safely affect the _txtSum textbox directly. Using the Invoke method, control is passed to the thread on which _txtSum was created. The UpdateSumDisplay is then run on that thread.

Note   Invoke is a blocking call. The thread calling Invoke blocks until the invoked method returns.

Limit the number of concurrent background tasks

Each executing thread consumes valuable device resources and increases application complexity. Launching a large number of concurrent background tasks can lead to a sluggish or unresponsive application. Run only those tasks in the background that would result in lengthy or uncomfortable delays if run in the foreground.

Provide Visual Cues

Background tasks run on a different thread then the user interface and therefore provide no automatic confirmation as to the state of the background processing. Applications must take specific, steps to keep the user informed. In many situations, it may be desirable to display a status field on the user form. This is useful for keeping the user informed of the ongoing status of the background process.

Indicating Startup

In most cases background processing is initiated in response to a user action such as button click. In this case, the control that initiated the background processing should be disabled until the processing completes. Disabling the control serves both as confirmation that the background process has started as well as preventing the user from inadvertently launching additional instances of the task. Any control that will be updated when the background task completes should be disabled and cleared.

Indicating Completion

Upon completion, the application should provide a clear indication without interfering with other work the user may be doing. Results from the processing should be made easily available and any controls that were disabled when the task began should be enabled. In cases such as a long running calculation where the background processing results in a few simple values, the results should be displayed directly on the form which initiated the processing. When the results are too complex to display easily on the same form, it’s better to enable a button that allows the user to view the results on a separate form. Even if the background task has no viewable results such as a large data upload, the user should still receive confirmation that the background task completed. The Pocket PC user notification API (SHNotificationAdd) is especially useful for this scenario as it provides a noticeable yet non-intrusive confirmation.

Conclusion

The .NET Compact Framework’s rich asynchronous Web service and threading support enable the development of increasingly sophisticated smart device applications by moving long running processing into the background. The decision to introduce background processing into an application requires careful planning and understanding of the options available and their implications. Used correctly, background processing improves application responsiveness and provides a superior user experience allowing users to reap the results of long running processes without being preventing from working while they run.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.