Writing Mobile Code Essential Software Engineering for Building Mobile Applications [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Writing Mobile Code Essential Software Engineering for Building Mobile Applications [Electronic resources] - نسخه متنی

Ivo Salmre

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید











Threads and User Interface


A question that occasionally gets asked is, "Should I use multiple user interface threads?" The answer is almost certainly "No." In almost no cases does it make sense for different parts of a user interface to be driven by different threads. This is particularly true for mobile devices where the application user interfaces are usually full screen windows.

Usually windows are tied to a thread that owns them; this is true for Windows CE, Pocket PC, and the Microsoft Smartphone OS (as well as for desktop operating systems such as Windows XP and earlier versions). Each window has a thread it belongs to and gets commands from. Multiple windows can be owned by the same thread. The thread serves as the "message pump" to the windows and sends them messages when the window needs to be painted, when a key is tapped, when the button is clicked, and so forth.

Although it is possible to have an application with a user interface running on multiple different threads (for example, one thread per top-level window), it is almost never a good idea. It will make your application structure much messier and will not make it run faster. If you find yourself needing more than one thread for your user interface, ask yourself what it is that you are really trying to accomplish and whether it could be done using one main user interface thread and multiple background worker threads; this is a much cleaner model.

More often than not, the intent of having multiple threads with user interface associated with each is driven by a desire to keep the user informed of the progress of tasks occurring on different background threads. It is far better to have a single foreground interface thread poll for this data periodically using a timer than it is to have multiple threads driving multiple windows on the screen. Designing your background tasks as classes with state machines makes this data easy to collect from the user interface thread. This is yet another good reason to use a state machine approach.

Even if your windowing model is not tied to any specific threads, it is generally a good idea to have one thread of execution that "takes care of the user interface."


On the .NET Compact Framework, Do Not Access User Interface Controls from Threads That They Do Not Belong To


Neither the desktop .NET Framework nor the .NET Compact Framework supports accessing most of the properties and methods of user interface elements from threads that they do not belong to. Although the code will compile the execution, results will not be predictable. To allow calling from in between threads, the .NET Framework and .NET Compact framework support a method called Control.Invoke(). The .NET Compact Framework v1.1 only supports using the Control.Invoke() mechanism to call functions with no parameters. See the MSDN documentation for this method for more information.

A reasonable and easy way to communicate data between a background thread and a user interface thread is to have code running on the user interface thread periodically poll an object you have specifically designed to manage background execution to see whether there is data waiting for the user interface. Doing this is generally easier than delving into the intricacies of cross-thread method invocation.

A second approach is to use a callback delegate that points to a function on your application's form. A function (without parameters, see above) in your form's class can be designated and this function can be called via the form's Invoke() method. This call to Invoke() will cause the function to be executed on the user interface's thread. The function can then retrieve whatever data it needs to and update the user interface accordingly. The upside of this approach is that the foreground thread does not need to poll and receives updates immediately when the background work is done. The downside of this approach is that it creates a (hopefully short) synchronous linkage between the foreground and background threads execution. When the delegate is executed on the background thread, the background thread's execution is paused, the execution context is switched to the foreground thread, and then the delegate is run. This prevents the background thread from moving on to other queued work. Only when the delegate's execution is complete is the background thread able to resume execution.

An Example Using Background Processing with Foreground Thread User Interface Updates


For this example, we will return to the "prime number" example introduced in Chapter 5, "Our Friend, the State Machine," and make some significant modifications and improvements. This time we will build a smart phone application that computes large prime numbers. The application will stay dynamic while a large number of calculations occur on a background thread. The application enables the user to abort the background thread if desired. Importantly, the application will also give the user a good understanding of the progress that is being made on his behalf by the application. This progress report is intended to keep the user satisfied that the background task is proceeding well.

This application can also easily be adapted to run on a Pocket PC. The choice of Microsoft Smartphone was simply for variety.

Figure 9.3. Runtime screenshots of the Smartphone emulator running the prime number calculation.

Appendix A, "Additional Resources for the .NET Compact Framework"). The SDK includes the necessary components for Smartphone user interface design as well as a Smartphone emulator that lets you run the applications without needing a physical Smartphone.

Do the following to build and run the application:


1.

Start Visual Studio .NET (2003 or later) and create a C# Smart Device Application.

2.

Choose Smartphone as the target platform. (A project will be created for you, and a Smartphone form designer will appear.)

3.

Using Figure 9.2 above as a layout model, add the following controls to the form:

A TextBox (textBox1); set its Text property to a large text string (for example, 12345678901234).

A Label (label1); resize the Label so it takes up most of the form's display area. It will need to display text that is several lines long.

A Timer control (timer1).


Figure 9.2. The Visual Studio development environment showing the Smartphone's user interface being designed.

[View full size image]

4.

Select the MainMenu component at the bottom of the form's designer window and add the following menu subitems:

On the left-side menu (where it says Type Here), change the menu text to read Exit. Using the Properties window, rename the menu item from menuItem1 to menuItemExit.

To the right of the Exit menu you just added (where it says Type Here), change the menu text to be Prime Search. Note: Use Figure 9.2 above as a guide if needed.

Above of the Prime Search menu you just added (where it says Type Here), change the menu text to be Start. Using the Properties window, rename the menu item from menuItem2 to menuItemStart. Note: Use Figure 9.2 above as a guide if needed.

Below of the Start menu you just added (where it says Type Here), change the menu text to be Abort. Using the Properties window, rename the menu item from menuItem2 to menuItemAbort. Note: Use Figure 9.2 above as a guide if needed.

5.

Add a new class to the project. Name it FindNextPrimeNumber.cs. Replace the contents in the class' code editor with the code in Listing 9.5.

6.

Go back to the Form1.cs [Design] window and double-click the menu item Exit. This will auto-generate a function called void menuItemExit_Click() for you and bring you to it in the code editor. Enter the code in Listing 9.4 for this function.

7.

Go back to the Form1.cs [Design] window and double-click the menu item Start. This will autogenerate a function called private void menuItemStart_Click() for you and bring you to it in the code editor. Enter the code in Listing 9.4 for this function.

8.

Go back to the Form1.cs [Design] window and double-click the menu item Abort. This will autogenerate a function called private void menuItemAbort_Click() for you and bring you to it in the code editor. Enter the code in Listing 9.4 for this function.

9.

Go back to the Form1.cs [Design] window and double-click timer1 control at the bottom of the designer. This will autogenerate a function called private void timer1_Tick() for you. Enter the code in Listing 9.4 for this function.

10.

Enter the rest of the code listed in Listing 9.4 into the Form1.cs class.

11.

Press F5 to compile and deploy the application to the Smartphone emulator.


When the application is running, you can press the phone button to bring up the Prime Search menu and then press the 1 key to select the Start submenu item. This sets the background search running. As the search runs, the timer1 control should generate a timer event several times a second that triggers the user interface to update the status text displayed on the form. These dynamic updates several times a second keep users informed that progress is being made on their request. The search continues until a prime number is found or the user selects the Abort menu item by selecting the Prime Search menu and pressing 2. To lengthen the time searches take, place a larger number into the text box on the form before starting a search. The number 12345678901234 runs for more than 20 seconds in my emulator. Presently, the following code does not prevent a new search from being started while an old one is still running. A good additional feature to add to the application below would be to check for this condition and to abort an existing search if it were already running. It is also worth looking at the code in Listing 9.5 to see how the lock keyword works to ensure that critical sections of code that are not thread-safe are not entered concurrently by different threads.

Listing 9.4. Code That Goes into the Smartphone Form1.cs class



//----------------------------------------------------
//All this code belongs inside a Form1.cs class
//----------------------------------------------------
//The object that will do our background calculation
FindNextPrimeNumber m_findNextPrimeNumber;
//---------------------------------------------------------
//Update the status text.
//---------------------------------------------------------
void setCalculationStatusText(string text)
{
label1.Text = text;
}
//---------------------------------------------------------
//Menu item for "Exiting" the application
//---------------------------------------------------------
private void menuItemExit_Click(object sender,
System.EventArgs e)
{
this.Close();
}
//---------------------------------------------------------
//Menu item for starting the background calculation
//---------------------------------------------------------
private void menuItemStart_Click(object sender,
System.EventArgs e)
{
//What number do we want to start looking at
long startNumber = System.Convert.ToInt64(textBox1.Text);
//Set up the background calculation
m_findNextPrimeNumber = new FindNextPrimeNumber(startNumber);
//Start the background processing running.
m_findNextPrimeNumber.findNextHighestPrime_Async();
//Set up the timer that will track the calculation
timer1.Interval = 400; //400 ms
timer1.Enabled = true;
}
//---------------------------------------------------------
//Menu item for "Aborting" a calculation under progress
//---------------------------------------------------------
private void menuItemAbort_Click(object sender,
System.EventArgs e)
{
//If we are not doing a calculation, do nothing.
if(m_findNextPrimeNumber == null) return;
//Set the thread up to abort
m_findNextPrimeNumber.setProcessingState(
FindNextPrimeNumber.ProcessingState.requestAbort);
//Let the user instantly know we are getting
//ready to abort...
setCalculationStatusText("Waiting to abort..");
}
//-------------------------------------------------------
//This timer gets called on the UI thread and allows
//us to keep track of progress on our background
//calculation
//-------------------------------------------------------
private void timer1_Tick(object sender, System.EventArgs e)
{
//If we get called and we have no prime number
//we are looking for, turn off the timer
if (m_findNextPrimeNumber == null)
{
timer1.Enabled = false;
return;
}
//--------------------------------------------
//If we've been aborted, throw out the prime seeker
//and turn off the timer
//--------------------------------------------
if (m_findNextPrimeNumber.getProcessingState ==
FindNextPrimeNumber.ProcessingState.aborted)
{
timer1.Enabled = false;
m_findNextPrimeNumber = null;
setCalculationStatusText("Prime search aborted");
return;
}
//--------------------------------------------
//Did we find the right answer?
//--------------------------------------------
if (m_findNextPrimeNumber.getProcessingState ==
FindNextPrimeNumber.ProcessingState.foundPrime)
{
timer1.Enabled = false;
//Show the result
setCalculationStatusText("Found! Next Prime = " +
m_findNextPrimeNumber.getPrime().ToString());
m_findNextPrimeNumber = null;
return;
}
//--------------------------------------------
//The calculation is progressing. Give the
//user an idea of the progress being made.
//--------------------------------------------
//Get the two output values
long numberCalculationsToFar;
long currentItem;
m_findNextPrimeNumber.getExecutionProgressInfo(
out numberCalculationsToFar, out currentItem);
setCalculationStatusText("In progress. Looking at: " +
currentItem.ToString() + ". " +
numberCalculationsToFar.ToString() +
" calculations done for you so far!");
}

Listing 9.5. Code for the FindNextPrimeNumber.cs Class



using System;
public class FindNextPrimeNumber
{
//States we can be in.
public enum ProcessingState
{
notYetStarted,
waitingToStartAsync,
lookingForPrime,
foundPrime,
requestAbort,
aborted
}
long m_startPoint;
long m_NextHighestPrime;
//How many items have been searched?
long m_comparisonsSoFar;
//What is the current item we are doing a prime search for?
long m_CurrentNumberBeingExamined;
//Called to get an update on how the calculation is progressing
public void getExecutionProgressInfo(
out long numberCalculationsSoFar,
out long currentItemBeingLookedAt)
{
//NOTE: We are using this thread lock to make sure that
//we are not reading these values while they are in the
//middle of being written out. Because m_comparisonsSoFar
//and m_CurrentNumberBeingExamined may be
//accessed from multiple threads, any read or write
//operation to them needs to be synchronized with "lock" to
//ensure that reads and writes are "atomic".
lock(this)
{
numberCalculationsSoFar = m_comparisonsSoFar;
currentItemBeingLookedAt = m_CurrentNumberBeingExamined;
}
}
ProcessingState m_processingState;
//--------------------------------------------------
//A very simple state machine.
//--------------------------------------------------
public void setProcessingState(ProcessingState nextState)
{
//--------------------------------------------------
//Some very simple protective code to make sure
//we can't enter another state if we have either
//Successfully finished, or Successfully aborted
//--------------------------------------------------
if ((m_processingState == ProcessingState.aborted)
|| (m_processingState == ProcessingState.foundPrime))
{
return;
}
//Allow the state change
m_processingState = nextState;
}
public ProcessingState getProcessingState
{
get {return m_processingState;}
}
//---------------------------------------------------
//Returns the prime
//---------------------------------------------------
public long getPrime()
{
if (m_processingState != ProcessingState.foundPrime)
{
throw new Exception("Prime number not calculated yet!");
}
return m_NextHighestPrime;
}
//Class constructor
public FindNextPrimeNumber(long startPoint)
{
setProcessingState(ProcessingState.notYetStarted);
m_startPoint = startPoint;
}
//------------------------------------------------------------
//Creates a new worker thread that will call
// "findNextHighestPrime()"
//------------------------------------------------------------
public void findNextHighestPrime_Async()
{
System.Threading.ThreadStart threadStart;
threadStart =
new System.Threading.ThreadStart(findNextHighestPrime);
System.Threading.Thread newThread;
newThread = new System.Threading.Thread(threadStart);
//Set our processing state to say that we are looking
setProcessingState(ProcessingState.waitingToStartAsync);
newThread.Start();
}
//------------------------------------------------------------
//This is the main worker function. This synchronously starts
//looking for the next prime number and does not exit until
//either:
// (a) The next prime is found
// (b) An external thread to this thread tells us to abort
//------------------------------------------------------------
public void findNextHighestPrime()
{
//If we've been told to abort, don't even start looking
if(m_processingState == ProcessingState.requestAbort)
{
goto finished_looking;
}
//Set our processing state to say that we are looking
setProcessingState(ProcessingState.lookingForPrime);
long currentItem;
//See if it's odd
if ((m_startPoint & 1) == 1)
{
//It's odd, start at the next odd number
currentItem = m_startPoint + 2;
}
else
{
//It's even, start at the next odd number
currentItem = m_startPoint + 1;
}
//Look for the prime item.
while(m_processingState == ProcessingState.lookingForPrime)
{
//If we found the prime item, return it
if(isItemPrime(currentItem) == true)
{
m_NextHighestPrime = currentItem;
//Update our state
setProcessingState(ProcessingState.foundPrime);
}
currentItem = currentItem + 2;
}
finished_looking:
//Exit. At this point we have either been
//Told to abort the search by another thread, or
//we have found and recorded the next highest prime number
//If an abort was requested, note that we have aborted
//the process.
if (m_processingState == ProcessingState.requestAbort)
{
setProcessingState(ProcessingState.aborted);
}
}
//Helper function that looks to see if a specific item
//is a prime number.
private bool isItemPrime(long potentialPrime)
{
//If it's even, it's not prime
if ((potentialPrime & 1) == 0)
{
return false;
}
//We want to look up until just past the square root
//of the item
long end_point_of_search;
end_point_of_search = (long) System.Math.Sqrt(potentialPrime) + 1;
long current_test_item = 3;
while(current_test_item <= end_point_of_search )
{
//---------------------------------
//Check to make sure we have not been asked to abort!
//---------------------------------
if (m_processingState != ProcessingState.lookingForPrime)
{
return false;
}
//If the item is divisible without remainder,
//it is not prime
if(potentialPrime % current_test_item == 0)
{
return false;
}
//advance by two
current_test_item = current_test_item + 2;
//------------------------------------------------------
//Up the count of items we have examined
//------------------------------------------------------
//NOTE: We are using this thread lock to make sure that
//we are not reading these values while they are in the
//middle of being written out. Because m_comparisonsSoFar
//and m_CurrentNumberBeingExamined may be
//accessed from multiple threads, any read or write
//operation to them needs to be synchronized with "lock" to
//ensure that reads and writes are "atomic".
lock(this)
{
m_CurrentNumberBeingExamined = potentialPrime;
m_comparisonsSoFar++;
}
}
//The item is prime
return true;
} //End function
} //End class


/ 159