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 SampleApplicationsChapter4. 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.151.44