Understanding Thread Basics


To use threads effectively, there are several fundamental practices that developers must understand, such as how to start threads and put threads to sleep. Additionally, there are some obscurities involved with using threads in the .NET Compact Framework. For example, using threads with user interface controls requires special care. Also, shutting down a multithreaded .NET Compact Framework application must be done in a specific fashion, or the application will not terminate.

In this section we discuss the basics that developers must master to use threads safely and effectively on the .NET Compact Framework. We'll wrap this section up with a sample application that demonstrates all of these concepts.

Creating and Starting a Thread

To create and start a thread with the .NET Compact Framework, follow these steps:

  1. Create an instance of System.Threading.ThreadStart . Pass in the name of the method to execute in a thread. The method must be void with no input arguments.

  2. Create an instance of System.Threading.Thread . Pass the ThreadStart into the Thread constructor. You now have a reference to the thread that will execute the method that was specified in step 1.

  3. Call Thread.Start() on the reference to the thread created in step 2. This causes the code in the method you will run on the thread to be begin executing.

Writing Code to Start a Thread

This sample code is from the SimpleThread sample application discussed later in the chapter. It demonstrates the launching of two threads, m_SinglesThread and m_DecadeThread. m_SinglesThread executes a method called SinglesCounter , which counts by ones, and m_DecadeThread executes a method called DecadeCounter , which counts by tens.

STARTING A THREAD

Remember that the thread does not actually start running until Thread.Start() is called.


 
 C# System.Threading.Thread m_singlesThread; System.Threading.ThreadStart m_singlesThreadStart; System.Threading.Thread m_decadeThread; System.Threading.ThreadStart m_decadeThreadStart; m_singlesThreadStart = new    System.Threading.ThreadStart(SinglesCounter); m_singlesThread = new System.Threading.Thread(m_singlesThreadStart); m_decadeThreadStart = new    System.Threading.ThreadStart(DecadeCounter); m_decadeThread = new System.Threading.Thread(m_decadeThreadStart); m_singlesThread.Start(); m_decadeThread.Start(); VB Dim m_singlesThread As System.Threading.Thread Dim m_singlesThreadStart As System.Threading.ThreadStart Dim m_decadeThread As System.Threading.Thread Dim m_decadeThreadStart As System.Threading.ThreadStart m_singlesThreadStart = New System.Threading.ThreadStart(AddressOf SinglesCounter) m_singlesThread = New System.Threading.Thread(m_singlesThreadStart) m_decadeThreadStart = New System.Threading.ThreadStart(AddressOf DecadeCounter) m_decadeThread = New System.Threading.Thread(m_decadeThreadStart) 

Suspending a Thread

To suspend a thread's execution, use the static Thread.Sleep() method, which suspends the thread running the block of code that called Thread.Sleep. Pass in the minimum number of milliseconds that the thread will sleep before it is placed in the ready queue to wait for more processor time.

If you pass 0 in as the argument for Thread.Sleep , then the current thread gives up the CPU but immediately goes back into the ready queue. It gets the processor again as soon as its turn comes up.

It is important to note that the number of milliseconds passed into Thread.Sleep specifies only the amount of time before the thread goes back into the ready queue. It could be considerably longer before the thread actually gets the CPU again, especially if the device is heavily loaded. Consider these snippets, for example:

 
 C# // The code running in myThread is suspended for at // least 1000 milliseconds Thread.Sleep(1000); VB ' The code running in myThread is suspended for ' at least 1000 milliseconds Thread.Sleep(1000) 

Using Threads with User Interface Controls

On the .NET Compact Framework, controls cannot be updated by threads that do not own the control. Specifically, this means you cannot start a thread and then update elements in your user interface from that thread by directly interacting with the controls. If you try to do this, your application will lock up at seemingly random times. For example, running this code outside of the main application thread will cause problems:

 
 C# // This can lock up an app if called outside of // the main thread this.txtTextBox1.Text = "I set the text"; VB ' This can lock up an app if called outside of ' the main thread Me.txtTextBox1.Text = "I set the text" 

The desktop .NET Framework includes the Form.BeginInvoke and Form.EndInvoke methods to help cope with this situation, but these methods are not supported in the .NET Compact Framework.

To deal with this situation, first create a delegate for a method that does the user interface updates. Then use Form.Invoke to call the delegate from the thread. This is a confusing and cumbersome process, and it works only for calling methods that accept no parameters.

Rather than dealing with this problem directly, we present a custom class named ControlInvoker , which threads can use to call methods. The methods can safely update controls in the user interface of your application. ControlInvoker also allows developers to pass parameters to the methods they want to call. ControlInvoker is used with permission and is available from the Web site http://samples.gotdotnet.com/quickstart/CompactFramework/doc/controlinvoker.aspx (special thanks to Microsoft's Seth Demsey, David Rasmussen, and Bruce Hamilton for providing ControlInvoker ).

Note that the ControlInvoker and MethodCallInvoker classes are available in the ControlInvokerSample namespace. To use ControlInvoker to invoke a method, follow these steps:

  1. Create an instance of the ControlInvoker class.

  2. Structure the methods you want to call with the following signature: void AnyMethod(object[] in_args) .

  3. Create an instance of a MethodCallInvoker class, passing the name of the method you want to call into the constructor.

  4. Call the ControlInvoker.Invoke() method. Pass the MethodCallInvoker as the first argument. Pass additional parameters that you want passed to the method you will invoke as additional arguments to ControlInvoker.Invoke() .

  5. The method will be called. The object array will hold the arguments passed into ControlInvoker.Invoke() . Extract the objects and cast them back to their original types before using them.

The following sample code is derived from the SimpleThread sample application. The setTextBox method is passed a string and a TextBox as an array of objects. It extracts the string and TextBox and sets the text of the TextBox to the string passed in. The SinglesCounter method is executed in a separate thread. It uses a ControlInvoker to call setTextBox and pass to it the TextBox whose text must be set and the string to set it to. By using the ControlInvoker , the code in SinglesCounter can update controls on the main form without risking locking up the application.

 
 C# private void setTextBox(object[] in_args) {    string in_text = (string)in_args[0];    System.Windows.Forms.TextBox in_textBox =       (System.Windows.Forms.TextBox)in_args[1];    in_textBox.Text = in_text; } private void SinglesCounter() {    int l_currentSinglesCount = 0;    while (!m_QuitRequested)    {       m_controlInvoker.Invoke (new MethodCallInvoker (setTextBox),          Convert.ToString(l_currentSinglesCount), this.txtSingle);       l_currentSinglesCount++;       System.Threading.Thread.Sleep(200);    } } VB Private Sub setTextBox(ByVal in_args() As Object)    Dim in_text As String    in_text = in_args(0)    Dim in_textBox As System.Windows.Forms.TextBox    in_textBox = in_args(1)    in_textBox.Text = in_text End Sub Private Sub SinglesCounter()    Dim l_currentSinglesCount As Int32    l_currentSinglesCount = 0    While (m_QuitRequested = False)       m_controlInvoker.Invoke(New MethodCallInvoker(AddressOf               setTextBox), Convert.ToString(l_currentSinglesCount),               Me.txtSingle)       l_currentSinglesCount = l_currentSinglesCount + 1       System.Threading.Thread.Sleep(200)    End While    m_SinglesThreadRunning = False    ' Last thread out closes form    If (m_DecadeThreadRunning = False) Then       m_controlInvoker.Invoke(New MethodCallInvoker(AddressOf          ShutDown))    End If End Sub 

Quitting Threaded Applications

On the .NET Compact Framework, applications should not be considered stopped unless all threads have terminated . If a thread is sitting in a loop and is unaware that the application is closing, it can hold up the process of closing the application and can make the application appear as though it is not responding to close requests . Thus, it is important for developers to write their threads in such a way that they are aware whether the application is trying to close.

One way to accomplish this is to override the Form.OnClosing() method and set a flag so that threads can find out if the application is trying to close. If a thread determines that the application is trying to close, it can determine whether it is the last thread to exit, closing the application if needed. The following sample code, taken from the SimpleThread sample application, demonstrates this practice:

 
 C# private void ShutDown(object[] arguments) {    this.Close(); } protected override void OnClosing(CancelEventArgs e) {    if (m_SinglesThreadRunning  m_DecadeThreadRunning)    {       e.Cancel = true;       MessageBox.Show("Will wait for threads to stop, then quit");       m_QuitRequested = true;    }    else    {       Close();    } } // Runs on separate thread private void SinglesCounter() {    int l_currentSinglesCount = 0;    while (!m_QuitRequested)    {       m_controlInvoker.Invoke (new MethodCallInvoker (setTextBox),          Convert.ToString(l_currentSinglesCount), this.txtSingle);       l_currentSinglesCount++;       System.Threading.Thread.Sleep(200);    }    m_SinglesThreadRunning = false;    // Last thread out closes form    if (m_DecadeThreadRunning == false)       m_controlInvoker.Invoke(new MethodCallInvoker(ShutDown)); } // Runs on separate thread private void DecadeCounter() {    int l_currentDecadeCount = 0;    while (!m_QuitRequested)    {       m_controlInvoker.Invoke (new MethodCallInvoker (setTextBox),          Convert.ToString(l_currentDecadeCount), this.txtDecade);       l_currentDecadeCount += 10;       System.Threading.Thread.Sleep(200);    }    m_DecadeThreadRunning = false;    // Last thread out closes form    if (m_SinglesThreadRunning == false)       m_controlInvoker.Invoke(new MethodCallInvoker(ShutDown)); } VB Private Sub ShutDown(ByVal arguments() As Object)    Me.Close() End Sub Protected Overrides Sub OnClosing(ByVal e As         System.ComponentModel.CancelEventArgs)    If (m_SinglesThreadRunning Or m_DecadeThreadRunning) Then       e.Cancel = True       MessageBox.Show("Will wait for threads to stop, then quit")       m_QuitRequested = True    Else       Close()    End If End Sub Private Sub SinglesCounter()    Dim l_currentSinglesCount As Int32    l_currentSinglesCount = 0    While (m_QuitRequested = False)       m_controlInvoker.Invoke(New MethodCallInvoker(AddressOf               setTextBox), Convert.ToString(l_currentSinglesCount),               Me.txtSingle)       l_currentSinglesCount = l_currentSinglesCount + 1       System.Threading.Thread.Sleep(200)    End While    m_SinglesThreadRunning = False    ' Last thread out closes form    If (m_DecadeThreadRunning = False) Then       m_controlInvoker.Invoke(New MethodCallInvoker(AddressOf               ShutDown))    End If End Sub Private Sub DecadeCounter()    Dim l_currentDecadeCount As Int32    l_currentDecadeCount = 0    While (m_QuitRequested = False)       m_controlInvoker.Invoke(New MethodCallInvoker(AddressOf               setTextBox), Convert.ToString(l_currentDecadeCount),               Me.txtDecade)       l_currentDecadeCount += 10       System.Threading.Thread.Sleep(200)    End While    m_DecadeThreadRunning = False    ' Last thread out closes form    If (m_SinglesThreadRunning = False) Then       m_controlInvoker.Invoke(New MethodCallInvoker(AddressOf               ShutDown))    End If End Sub 

The ShutDown method wraps the Form.Close method so that it can be called using the ControlInvoker . The OnClosing method is overridden. It checks to see whether any child threads are running. If they are, a flag is set, indicating that the application wants to shut down. If none of the threads is running, then the application simply quits.

The SinglesCounter and DecadeCounter methods each run in a separate thread. Their loops break if the m_QuitRequested flag is set. When the threads fall out of the loop, each checks to see if another is running. The last thread still alive uses the MethodInvoker to call ShutDown , which closes the application.

Coding a Multithreaded Application: SimpleThread

The SimpleThread sample application is located in the folder \SampleApplications\Chapter4 . There are C# and Visual Basic versions of SimpleThread.

This application demonstrates how to create and start threads by creating two new threads. One counts by ones, and the other by tens. Both threads sleep briefly after updating the count.

Each thread updates the user interface after each count update by using the ControlInvoker . SimpleThread also uses the ControlInvoker to shut down gracefully, as discussed in the previous section.



Microsoft.NET Compact Framework Kick Start
Microsoft .NET Compact Framework Kick Start
ISBN: 0672325705
EAN: 2147483647
Year: 2003
Pages: 206

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net