C H A P T E R  22

Image

Introduction to Asynchronous Programming

Image Processes, Threads, and Asynchronous Programming

Image Parallel Loops

Image The BackgroundWorker Class

Image Asynchronous Programming Patterns

Image BeginInvoke and EndInvoke

Image Timers

Processes, Threads, and Asynchronous Programming

In this chapter, we're going to introduce four methods you can use to add multithreading to your programs. This chapter is a bit different from the previous chapters in that it goes beyond just the language features. Instead, we'll also include classes from the BCL and include some programming techniques. In spite of the fact that these things are a bit beyond just the language features, I want to do this because it's imperative that we as programmers increase our use of multiprocessing in our code—and I think a first book on C# is a good place to start.

When you start a program, the system creates a new process in memory. A process is the set of resources that comprise a running program. These include the virtual address space, file handles, and a host of other things required for the program to run.

Inside the process, the system creates a kernel object, called a thread, which represents the actual executing program. (Thread is short for “thread of execution.”) Once the process is set up, the system starts execution of the thread at the first statement in method Main.

Some important things to know about threads are the following:

  • By default, a process contains only a single thread, which executes from the beginning of the program to the end.
  • A thread can spawn other threads so that at any time, a process might have multiple threads in various states, executing different parts of the program.
  • If there are multiple threads in a process, they all share the process's resources.
  • It's threads, not processes, that are the units scheduled by the system for execution on the processor.

All the sample programs shown so far in this book have used only a single thread and have executed sequentially from the first statement in the program to the last. This is called synchronous programming. Asynchronous programming refers to programs that spawn multiple threads, which are, at least conceptually, executed at the same time. (They might not actually be executed at the same time.)

If the program is running on a multiprocessor system, the different threads might actually be executing at the same time on different processors. This can considerably improve performance, and as multicore processors become the norm, we need to write our programs to take advantage of this opportunity.

On a single-processor system, though, clearly only one instruction can be executed by the processor at a time. In this case, the operating system coordinates the threads so that the processor is shared among them. Each thread gets the processor for a short time, called a time slice, before being kicked off the processor and sent to the back of the line. This round-robin sharing of the processor lets all the threads work their ways through the code.

Multithreading Considerations

Using multiple threads in a program, called multithreading, or just threading, creates program overhead and additional program complexity. Here are some examples:

  • There are time and resource costs in both creating and destroying threads.
  • The time required for scheduling threads, loading them onto the processor, and storing their states after each time slice is pure overhead.
  • Since the threads in a process all share the same resources and heap, it adds additional programming complexity to ensure that they're not stepping on each other's work.
  • Debugging multithreaded programs can be quite difficult, since the timing on each run of the program can be different, producing different results. And the act of running the program in a debugger blows the timing out of the water.

In spite of these considerations, the benefits of threading can outweigh its costs, as long as it's used wisely—and not overused. For example, you've already seen that on a multiprocessor system, if the different threads can be placed on different processors, it can result in a much more efficient execution.

To help alleviate some of the costs associated with creating and destroying threads, the CLR maintains a thread pool for each process. Initially, a process's thread pool is empty, but after a thread is created and used by a process and then the thread completes its execution, it isn't destroyed but instead added to the process's thread pool. Later, if the process needs another thread, the CLR recycles one from the pool, saving a significant amount of time.

Another common example where multithreading is crucial is in graphical user interface (GUI) programming, where users expect a quick response any time they click a button or use the keyboard. In this case, if the program needs to perform an operation that's going to take any appreciable time, it should perform that operation on another thread, leaving the main thread available to respond to the user's input. It would be totally unacceptable to have the program unresponsive during that time.

The Complexity of Multithreading

Although multithreading is conceptually easy, getting all the details right can be frustratingly difficult on nontrivial programs. The areas that need to be considered are the following:

  • Communicating between the threads: There are few built-in mechanisms for communicating between threads, so this is often done simply using their shared memory, since the memory space is visible and accessible by all threads in the same process.
  • Coordinating threads: Although it's easy to create threads, you also need to be able to coordinate their actions. For example, a thread might need to wait for one or more other threads to complete before it can continue its execution.
  • Synchronization of resource usage: Since all the threads in a process share the same resources and memory, you need to make sure that the different threads aren't accessing and changing them at the same time, causing state inconsistencies.

The System.Threading namespace contains classes and types that you can use to build complex multithreaded systems. These include the Thread class itself and classes such as Mutex, Semaphore, and Monitor, which are used to synchronize resource usage. The use, complexities, and nuances of this tricky subject are beyond the scope of this text, and you'd be better advised to settle down with an in-depth book on the subject.

Parallel Loops

.NET 4.0 has introduced a new library, called the Task Parallel Library, which greatly simplifies parallel programming. This is a huge advance and includes a large amount of material—far more than I can cover in this chapter. So unfortunately, I've had to settle by just whetting your appetite by introducing just two of its very simple constructs that you can learn and use quickly and easily. These are the Parallel.For loop and the Parallel.ForEach loop. These constructs are in the System.Threading.Tasks namespace.

By this point in the book I'm sure you're quite familiar with C#'s standard for and foreach loops. These are common and tremendously powerful constructs. Many times when using these constructs, each iteration depends on a calculation or action in the previous iteration. But this isn't always the case. When the iterations are independent, it would be a huge advantage if you could put different iterations on different processors and process them in parallel. This is exactly what the Parallel.For and Parallel.ForEach constructs do.

These constructs are in the form of methods with input parameters. There are 12 overloads of the Parallel.For method, but the simplest has the following signature:

  • The fromInclusive parameter is the first integer in the iteration series.
  • The toExclusive parameter is an integer that is one greater than the last index in the iteration series. That is, it's the same as comparing on the expression index < ToExclusive.
  • The body is a delegate that takes a single input parameter. The code of body is executed once per iteration.
void Parallel.For( int fromInclusive, int toExclusive, Action body);

The following code is an example using the Parallel.For construct. It iterates from 0 to 15 and prints out the iteration index and the square of the index. Notice that it fits the requirement that each iteration is independent of any other iteration. Notice also that you must use the System.Threading.Tasks namespace.

using System;
using System.Threading.Tasks;         // Must use this namespace

namespace ExampleParallelFor
{
   class Program
   {
      static void Main( )
      {
         Parallel.For( 0, 15, i =>        
            Console.WriteLine( "The square of {0} is {1}", i, i * i ));
      }
   }
}

One run of this code on a PC with a two-core processor produced the following output. Notice that you're not guaranteed any particular order of the iterations.


The square of 0 is 0
The square of 7 is 49
The square of 8 is 64
The square of 9 is 81
The square of 10 is 100
The square of 11 is 121
The square of 12 is 144
The square of 13 is 169
The square of 3 is 9
The square of 4 is 16
The square of 5 is 25
The square of 6 is 36
The square of 14 is 196
The square of 1 is 1
The square of 2 is 4

Another example is the following code. This program fills an integer array, in parallel, with the square of the iteration index.

class Program
   {
      static void Main()
      {
         const int maxValues = 50;
         int[] squares = new int[maxValues];

         Parallel.For( 0, maxValues, i =< squares[i] = i * i );
      }
   }

Unlike the previous example, even though the iterations might be executed in parallel and in any order, the end result is an array containing the first 50 squares.

The other parallel loop construct is the Parallel.ForEach method. There are more than a dozen overloads for this method, but the simplest is the following:

  • The TSource is the type of object in the collection.
  • The source is the collection of TSource objects.
  • The body is the lambda expression to be applied to each element of the collection.
static ParallelLoopResult ForEach<TSource>( IEnumerable<TSource> source,
                                            Action<TSource> body)

An example of using the Parallel.ForEach method is the following code. In this case, TSource is string, and the source is a string[].

using System;
   using System.Threading.Tasks;

   namespace ParallelForeach1
   {
      class Program
      {
         static void Main()
         {
            string[] squares = new string[]
                     { "We", "hold", "these", "truths", "to", "be", "self-evident",
                       "that", "all", "men", "are", "created", "equal"};

            Parallel.ForEach( squares,
               i => Console.WriteLine( string.Format("{0} has {1} letters", i, i.Length) ));
         }
      }
   }

One run of this code on a PC with a two-core processor produced the following output, but the order might change each time:


"We" has 2 letters
"equal" has 5 letters
"truths" has 6 letters
"to" has 2 letters
"be" has 2 letters
"that" has 4 letters
"hold" has 4 letters
"these" has 5 letters
"all" has 3 letters
"men" has 3 letters
"are" has 3 letters
"created" has 7 letters
"self-evident" has 12 letters

The BackgroundWorker Class

Although much of asynchronous programming is complex, the BackgroundWorker class makes it simple to perform a task in the background on a separate thread. This class was designed primarily for GUI programming (Windows Forms and WPF) to allow them to offload time-consuming tasks from the main thread to a background thread. Figure 22-1 illustrates the key members of the class. The following is an overview of these members:

  • The first two properties shown in the figure are used to set whether the background task can report its progress to the main thread and whether it supports cancellation from the main thread. You use the third property to find out whether the background task is running.
  • The class has three events, which are used to signal different program events and states. You need to write event handlers for these events to take whatever actions are appropriate for your program.
    • The DoWork event is raised when the background thread starts.
    • The ProgressChanged event is raised when the background task reports progress.
    • The RunWorkerCompleted event is raised when the background worker exits.
  • The three methods are used to initiate actions or change state.
    • Calling the RunWorkerAsync method retrieves a background thread that executes the DoWork event handler.
    • Calling the CancelAsync method sets the CancellationPending property to true, potentially, although not necessarily, canceling the thread. It is the responsibility of the DoWork event handler to inspect this property to determine whether it should stop its processing.
    • The ReportProgress method can be called by the DoWork event handler (from the background thread)  when it wants to report its progress to the main thread.
Image

Figure 22-1. The key members of the BackgroundWorker class

To use a BackgroundWorker class object, you need to write the following event handlers. The first is required since it contains the code you want to be executed by the background thread, but the other two are optional, depending on the needs of your program.

  • The handler attached to the DoWork event contains the code you want executed in the background on a separate thread.
    • In Figure 22-2, this handler is named DoTheWork and is in a gradient-shaded box to illustrate that it's executed in the separate thread.
    • The DoWork event is raised when the main thread calls the RunWorkerAsync method.
  • The handler attached to the ProgressChanged event should contain the code to be executed on the main thread when the background task reports its progress.
    • The ProgressChanged event is raised when the background process calls the ReportProgress method.
    • Calling the ReportProgress method is how the background thread communicates with the main thread.
  • The handler attached to the RunWorkerCompleted event should contain the code to be executed on the main thread after the background thread completes the execution of the DoWork event handler.

Figure 22-2 shows the structure of your program, with the event handlers attached to the events of the BackgroundWorker object.

Image

Figure 22-2. Your code supplies event handlers for the events that control the flow through execution of the tasks.

The delegates for these event handlers are the following. Each takes an object reference as the first parameter and a specialized subclass of the EventArgs class as the second parameter.

void DoWorkEventHandler             ( object sender, DoWorkEventArgs e )

   void ProgressChangedEventHandler    ( object sender, ProgressChangedEventArgs e )

   void RunWorkerCompletedEventHandler ( object sender, RunWorkerCompletedEventArgs e)

Figure 22-3 illustrates the structure of the EventArg classes used by these event handlers.

Image

Figure 22-3. The EventArg classes used by the BackgroundWorker event handlers

When you have the event handlers written and attached to their events, you can use the class by doing the following:

  • Start by creating an object of the BackgroundWorker class and configuring it.
    • If you want the worker thread to communicate progression to the main thread, then set the WorkerReportsProgress property to true.
    • If you want to be able to cancel the worker thread from the main thread, then set the WorkerSupportsCancellation property to true.
  • Now that the object is configured, you can start it by calling the object's RunWorkerAsync method. This retrieves a background thread that raises the DoWork event and executes the event's handler in the background.

Now you have both the main thread and the background thread running. While the background thread is running, you can continue processing on the main thread.

  • In the main thread, if you've enabled the WorkerSupportsCancellation property, then you can call the object's CancelAsync method. This does not cancel the background thread! Instead, it sets the object's CancellationPending property to true that needs to be checked by the DoWork event handler code running on the background thread.
  • The background thread, in the meantime, continues to perform its computational tasks, as well as doing the following:
    • If the WorkerReportsProgress property is true and the background thread has progress to report to the main thread, then it must call the BackgroundWorker object's ReportProgress method. When the background thread calls the ReportProgress method, this raises the ProgressChanged event in the main thread, which runs the corresponding event handler.
    • If the WorkerSupportsCancellation property is enabled, then the DoWork event handler code should regularly check the  CancellationPending property to determine whether it has been canceled. If so, it should exit.
    • If the background thread finishes its processing without being canceled, it can return a result to the main thread by setting the Result field in the DoWorkEventArgs parameter shown previously, in Figure 22-3.
  • When the background thread exits, the RunWorkerCompleted event is raised, and its handler is executed on the main thread. The RunWorkerCompletedEventArgs parameter can contain information from the now completed background thread, such as the return value and whether the thread was canceled.

Example Code Using the BackgroundWorker Class

Although the BackgroundWorker class was designed for GUI programming, I'll start by showing its use with a console program since that's what we've used throughout the book.  In the next section, I'll show an example with a GUI program.

This program creates a background thread that sums a sequence of numbers. Several times during the process it checks to see whether it has been canceled. If it finds it's been canceled, it cleans up and exits. Otherwise, if it goes to completion, it stores the total in the Result field and exits.

Meanwhile, the main thread sums its own sequence of numbers and reports its total, along with the result from the background thread.

using System;
   using System.ComponentModel;                         // Must have this namespace
   using System.Threading;                              // Must have this namespace

   namespace ConsoleBackgroundWorker
   {
      class DoBackgroundwork
      {
         BackgroundWorker bgWorker = new BackgroundWorker();

         public long BackgroundTotal   { get; private set; }
         public bool CompletedNormally { get; private set; }

         // Constructor
         public DoBackgroundwork()
         {
            // Set BackgroundWorker properties
            bgWorker.WorkerReportsProgress      = true;
            bgWorker.WorkerSupportsCancellation = true;

            // Connect handlers to BackgroundWorker object.
            bgWorker.DoWork             += DoWork_Handler;
            bgWorker.ProgressChanged    += ProgressChanged_Handler;
            bgWorker.RunWorkerCompleted += RunWorkerCompleted_Handler;
         }

         public void StartWorker()
         {
            if ( !bgWorker.IsBusy )
               bgWorker.RunWorkerAsync();
         }

         // This just calculates the sum of the integers from 0 to the input value.
         public static long CalculateTheSequence( long value )
         {
            long total = 0;
            for ( int i=0; i < value; i++ )
               total += i;
            return total;
         }
                                                                          Image
public void DoWork_Handler( object sender, DoWorkEventArgs args )
         {
            BackgroundWorker worker = sender as BackgroundWorker;

            // Do the background calculation
            long total = 0;
            for ( int i = 1; i <= 5; i++ )
            {
               // Each time through the loop, check to see if we've been cancelled
               if ( worker.CancellationPending )
               {
                  args.Cancel = true;
                  worker.ReportProgress( -1 );
                  break;
               }
               else
               {
                  // If we haven't been cancelled, then continue the calculation.
                  total += CalculateTheSequence( i * 10000000 );
                  worker.ReportProgress( i * 20 );

                  // Slow the program down to a more comfortable output rate
                  // just for this demo.
                  Thread.Sleep( 300 );
               }
            }

            args.Result = total;       // Store the result and exit.
         }

         // Handle input from background thread.
         private void ProgressChanged_Handler
                        ( object sender, ProgressChangedEventArgs args )
         {
            string output
                  = args.ProgressPercentage == -1
                        ? "             Cancelled"
                        : string.Format("             {0}%", args.ProgressPercentage );

            Console.WriteLine( output );
         }

         // On completion of background thread, summarize and store the result.
         private void RunWorkerCompleted_Handler
                        ( object sender, RunWorkerCompletedEventArgs args )
         {
            CompletedNormally = !args.Cancelled;
            BackgroundTotal   =  args.Cancelled
                                       ? 0
                                       : (long) args.Result;   // Cast from object
         }
                                                                           Image
public void Cancel()
         {
            if( bgWorker.IsBusy )
               bgWorker.CancelAsync();
         }
      }

      class Program
      {
         static void Main()
         {
            GiveInstructionsToTheUser();
            OutputTheSummaryHeaders();

            // Create and Start the background worker
            DoBackgroundwork bgw = new DoBackgroundwork();
            bgw.StartWorker();

            // Start the computation on the main thread.  Each time through the loop,
            // check to see whether the user has cancelled the background thread.
            // After the calculation, add a short sleep, just to slow the program
            // down enough so the main thread doesn't run faster than the background.
            long mainTotal = 0;
            for ( int i = 0; i < 5; i++ )
            {
               if ( Program.CheckForCancelInput() )
                  bgw.Cancel();

               mainTotal += DoBackgroundwork.CalculateTheSequence( 100000000 );
               Thread.Sleep( 200 );
               Console.WriteLine( "    {0}%", (i+1) * 20 );
            }

            SummarizeResults( bgw, mainTotal );
            Console.ReadLine();
         }

         private static void GiveInstructionsToTheUser()
         {
            Console.WriteLine( "Press <Enter> to start background worker." );
            Console.WriteLine( "Press <Enter> again to cancel background worker." );
            Console.ReadLine();
         }

         private static void OutputTheSummaryHeaders()
         {
            Console.WriteLine( "    Main   Background" );
            Console.WriteLine( "---------------------" );
         }

                                                                           Image
private static void SummarizeResults( DoBackgroundwork bgw, long mainTotal )
         {
            if ( bgw.CompletedNormally )
            {
               Console.WriteLine( " Background completed Normally" );
               Console.WriteLine( "Background total = {0}", bgw.BackgroundTotal );
            }
            else
            {
               Console.WriteLine( " Background       Cancelled" );
            }

            Console.WriteLine( "Main total       = {0}", mainTotal );
         }

         private static bool CheckForCancelInput()
         {
            bool doCancel = Console.KeyAvailable;
            if ( doCancel )
               Console.ReadKey();
            return doCancel;
         }
      }
   }

This code produces the following results when allowed to run to completion.


Press <Enter> to start background worker.
Press <Enter> again to cancel background worker.

    Main   Background
---------------------
             20%
             40%
    20%
             60%
    40%
             80%
    60%
             100%
    80%
    100%

Background completed Normally
Background total = 2749999925000000
Main total       = 24999999750000000

Example of the BackgroundWorker Class in a WPF Program

Since the BackgroundWorker class is primarily used with GUI programming, the following program shows its use in a simple WPF program rather than the console programs we've used throughout the text. WPF is Microsoft's replacement for the Windows Forms GUI programming framework. For further information about WPF programming, please see my book Illustrated WPF, also published by Apress.

This program produces the window shown on the left in Figure 22-4. When you click the Process button, it starts the background thread, which reports to the main thread every half second and increments the progress bar at the top by 10 percent. At completion, it shows the dialog box on the right of Figure 22-4.

Image

Figure 22-4. The example WPF program using the BackgroundWorker class

To create this WPF program in Visual Studio 2010, do the following:

  1. Select the File Image New Image Project menu item, which pops up the New Project window.
  2. In the pane on the left of the window, open the Installed Templates section, if it's not already open.
  3. Under the C# category, click the Windows entry. This populates the center pane with the installed Windows program templates.
  4. Click WPF Application, and then at the bottom of the window enter SimpleWorker in the Name text box. Below that, select a location, and click the OK button.

There are only two files you'll modify—MainWindow.xaml and MainWindow.xaml.cs. Modify your MainWindow.xaml file to match the following listing:

<Window x:Class="SimpleWorker.MainWindow"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           Title="MainWindow" Height="150  " Width="250">
      <StackPanel>
         <ProgressBar Name="progressBar" Height="20" Width="200" Margin="10"/>
         <Button Name="btnProcess" Width="100" Click="btnProcess_Click"  
                 Margin="5">Process</Button>
         <Button Name="btnCancel" Width="100" Click="btnCancel_Click"
                 Margin="5">Cancel</Button>
      </StackPanel>
   </Window>

Modify your MainWindow.xaml.cs file to match the following listing:

using System.Windows;
   using System.ComponentModel;
   using System.Threading;

   namespace SimpleWorker
   {
      public partial class MainWindow : Window
      {
         BackgroundWorker bgWorker = new BackgroundWorker();

         public MainWindow()
         {
            InitializeComponent();

            // Set BackgroundWorker properties
            bgWorker.WorkerReportsProgress      = true;
            bgWorker.WorkerSupportsCancellation = true;

            // Connect handlers to BackgroundWorker object.
            bgWorker.DoWork             += DoWork_Handler;
            bgWorker.ProgressChanged    += ProgressChanged_Handler;
            bgWorker.RunWorkerCompleted += RunWorkerCompleted_Handler;
         }

         private void btnProcess_Click( object sender, RoutedEventArgs e )
         {
            if ( !bgWorker.IsBusy )
               bgWorker.RunWorkerAsync();
         }

         private void ProgressChanged_Handler( object sender,
                                               ProgressChangedEventArgs args )
         {
            progressBar.Value = args.ProgressPercentage;
         }
                                                                         Image
private void DoWork_Handler( object sender, DoWorkEventArgs args )
         {
            BackgroundWorker worker = sender as BackgroundWorker;

            for ( int i = 1; i <= 10; i++ )
            {
               if ( worker.CancellationPending )
               {
                  args.Cancel = true;
                  break;
               }
               else
               {
                  worker.ReportProgress( i * 10 );
                  Thread.Sleep( 500 );
               }
            }
         }

         private void RunWorkerCompleted_Handler( object sender,
                                                  RunWorkerCompletedEventArgs args )
         {
            progressBar.Value = 0;

            if ( args.Cancelled )
               MessageBox.Show( "Process was cancelled.", "Process Cancelled" );
            else
               MessageBox.Show( "Process completed normally.", "Process Completed" );
         }

         private void btnCancel_Click( object sender, RoutedEventArgs e )
         {
            bgWorker.CancelAsync();
         }
      }
   }

Asynchronous Programming Patterns

In Chapter 15, we covered the topic of delegates, and you saw that when a delegate object is invoked, it invokes the methods contained in its invocation list. This is done synchronously, just as if the methods had been called by the program.

If a delegate object has only a single method (which I'll call the referenced method) in its invocation list, it can execute that method asynchronously. The delegate class has two methods, called BeginInvoke and EndInvoke, that are used to do this. You use these methods in the following way:

  • When you call the delegate's BeginInvoke method, it starts its referenced method executing on a separate thread from the thread pool and then returns immediately to the initial thread. The initial thread then continues on while the referenced method executes in parallel.
  • When your program wants to retrieve the results of the completed asynchronous method, it either checks the IsCompleted property of the IAsyncResult returned by BeginInvoke or calls the delegate's EndInvoke method to wait for the delegate to finish.

Figure 22-5 shows the three standard patterns for using this process. In all three patterns, the initial thread initiates an asynchronous method call and then does some additional processing. The patterns differ, however, in the ways in which the initial thread receives the information that the spawned thread has completed.

  • In the wait-until-done pattern, after spawning the asynchronous method and doing some additional processing, the initial thread halts and waits for the spawned thread to finish before continuing.
  • In the polling pattern, the initial thread checks periodically whether the spawned thread has completed, and if not, it continues additional processing.
  • In the callback pattern, the initial thread continues execution without waiting or checking whether the spawned thread has completed. Instead, when the referenced method in the spawned thread finishes, it calls a callback method, which handles the results of the asynchronous method before calling EndInvoke.
Image

Figure 22-5. The standard patterns for asynchronous method calls

BeginInvoke and EndInvoke

Before we look at examples of these asynchronous programming patterns, let's take a closer look at the BeginInvoke and EndInvoke methods. Some of the important things to know about BeginInvoke are the following:

  • When calling BeginInvoke, the actual parameters in the parameter list consist of the following:
    • The parameters required by the referenced method
    • Two additional parameters, called the callback parameter and the state parameter
  • BeginInvoke retrieves a thread from the thread pool and starts the referenced method running on the new thread.
  • BeginInvoke returns to the calling thread a reference to an object implementing the IAsyncResult interface. This interface reference contains information about the current state of the asynchronous method. The initial thread then continues execution.

The following code shows an example of calling a delegate's BeginInvoke method. The first line declares a delegate type called MyDel. The next line declares a method called Sum, which matches the delegate.

  • The following line declares a delegate object called del, of the delegate type MyDel, and initializes its invocation list with the Sum method.
  • Finally, the last line of code calls the BeginInvoke method of the delegate object and supplies it with the two delegate parameters 3 and 5 and the two BeginInvoke parameters callback and state, which are set to null in this example. When executed, the BeginInvoke method performs two actions:
    • It gets a thread from the thread pool and starts method Sum running on the new thread, supplying it with 3 and 5 as its actual parameters.
    • It collects information about the state of the new thread and makes it available through a reference to an interface of type IAsyncResult, which it returns to the calling thread. The calling thread, in this example, stores it in a variable called iar.

Image

You use the EndInvoke method to retrieve the values returned by the asynchronous method call and to release resources used by the thread. EndInvoke has the following characteristics:

  • It takes as a parameter the reference to the IAsyncResult returned by the BeginInvoke method and finds the thread it refers to.
  • If the thread pool thread has exited, EndInvoke does the following:
    • It cleans up the exited thread's loose ends and disposes of its resources.
    • It finds the value returned by the referenced method and returns that value as its return value.
  • If the thread pool thread is still running when EndInvoke is called, the calling thread stops and waits for it to finish before cleaning up and returning the value. Because EndInvoke cleans up after the spawned thread, you must make sure that an EndInvoke is called for each BeginInvoke.
  • If the asynchronous method triggers an exception, the exception is raised when EndInvoke is called.

The following line of code shows an example of calling EndInvoke to retrieve the value from an asynchronous method. You must always include the reference to the IAsyncResult object as a parameter.

Image

EndInvoke supplies all the output from the asynchronous method call, including ref and out parameters. If a delegate's referenced method has ref or out parameters, they must be included in EndInvoke's parameter list before the reference to the IAsyncResult object, as shown here:

Image

The Wait-Until-Done Pattern

Now that you understand the BeginInvoke and EndInvoke delegate methods, we can look at the asynchronous programming patterns. The first one we'll look at is the wait-until-done pattern. In this pattern, the initial thread initiates an asynchronous method call, does some additional processing, and then stops and waits until the spawned thread finishes. It's summarized as follows:

IAsyncResult iar = del.BeginInvoke( 3, 5, null, null );
        // Do additional work in the calling thread, while the method
        // is being executed asynchronously in the spawned thread.
        ...
   long result = del.EndInvoke( iar );

The following code shows a full example of this pattern. This code uses the Sleep method of the Thread class to suspend itself for 100 milliseconds (1/10 of a second). The Thread class is in the System.Threading namespace.

using System;
   using System.Threading;                         // For Thread.Sleep()

   delegate long MyDel( int first, int second );   // Declare delegate type
  
   class Program {
      static long Sum(int x, int y)                // Declare method for async
      {
         Console.WriteLine("                       Inside Sum");
         Thread.Sleep(100);
  
         return x + y;
      }

      static void Main( ) {
         MyDel del = new MyDel(Sum);
  
         Console.WriteLine( "Before BeginInvoke" );
         IAsyncResult iar = del.BeginInvoke(3, 5, null, null); // Start async
         Console.WriteLine( "After  BeginInvoke" );
  
         Console.WriteLine( "Doing stuff" );
  
         long result = del.EndInvoke( iar );    // Wait for end and get result
         Console.WriteLine( "After  EndInvoke: {0}", result );
      }
   }

This code produces the following output:


Before BeginInvoke
After  BeginInvoke
Doing stuff
                       Inside Sum
After  EndInvoke: 8

The AsyncResult Class

Now that you've seen BeginInvoke and EndInvoke in action in their simplest forms, it's time to take a closer look at IAsyncResult, which is an integral part of using these methods.

BeginInvoke returns a reference to an IAsyncResult interface that is inside a class object of type AsyncResult. The AsyncResult class represents the state of the asynchronous method. Figure 22-6 shows a representation of some of the important parts of the class. The important things to know about the class are the following:

  • When you call a delegate object's BeginInvoke method, the system creates an object of the class AsyncResult. It doesn't, however, return a reference to the class object. Instead, it returns a reference to the interface contained in the object—IAsyncResult.
  • An AsyncResult object contains a property called AsyncDelegate, which returns a reference to the delegate that was invoked to start the asynchronous method. This property, however, is part of the class object but not part of the interface.
  • The IsCompleted property returns a Boolean value indicating whether the asynchronous method has completed.
  • The AsyncState property returns a reference to the object that was listed as the state parameter in the BeginInvoke method invocation. It returns a reference of type object. I'll explain this in the section on the callback pattern.
Image

Figure 22-6. An AsyncResult class object

The Polling Pattern

In the polling pattern, the initial thread initiates an asynchronous method call, does some additional processing, and then uses the IsCompleted method of the IAsyncResult object to check periodically whether the spawned thread has completed. If the asynchronous method has completed, the initial thread calls EndInvoke and continues. Otherwise, it does some additional processing and checks again later. The “processing” in this example just consists of counting from 0 to 10,000,000.

Image

This code produces the following output:


After BeginInvoke
Not Done
                  Inside Sum
Not Done
Not Done
Done
Result: 8

The Callback Pattern

In the previous two patterns, wait-until-done and polling, the initial thread continues with its flow of control only after it knows that the spawned thread has completed. It then retrieves the results and continues.

The callback pattern is different in that once the initial thread spawns the asynchronous method, it goes on its way without synchronizing with it again. When the asynchronous method call completes, the system invokes a user-supplied method to handle its results and to call the delegate's EndInvoke method. This user-defined method is called a callback method, or just a callback.

The two extra parameters at the end of the BeginInvoke parameter list are used with the callback method as follows:

  • The first of the two parameters, the callback parameter, is the name of the callback method.
  • The second parameter, the state parameter, can be either null or a reference to an object you want passed into the callback method. You'll be able to access this object through the method's IAsyncResult parameter using its AsyncState property. The type of this parameter is object.
The Callback Method

The signature and return type of the callback method must be of the form described by the AsyncCallback delegate type. This form requires that the method take a single parameter of type IAsyncResult and have a void return type, as shown here:

void AsyncCallback( IAsyncResult iar )

There are several ways you can supply the callback method to the BeginInvoke method. Since the callback parameter in BeginInvoke is a delegate of type AsyncCallback, you can supply it as a delegate, as shown in the first code statement that follows. Or you can just supply the name of the callback method and let the compiler create the delegate for you. Both forms are semantically equivalent.

Image

The second BeginInvoke parameter is used to send an object to the callback method. It can be an object of any type, but the parameter is of type object, so inside the callback method you'll have to cast it to the correct type.

Calling EndInvoke Inside the Callback Method

Inside the callback method, your code should call the delegate's EndInvoke method and take care of handling the output results of the asynchronous method execution. To call the delegate's EndInvoke method, though, you need a reference to the delegate object, which is in the initial thread—not here in the spawned thread.

If you're not using BeginInvoke's state parameter for anything else, you can use it to send the delegate reference to the callback method, as shown here:

Image

Otherwise, you can extract the delegate's reference from the IAsyncResult object sent into the method as the parameter. This is shown in the following code and illustrated in Figure 22-7.

  • The single parameter to the callback method is a reference to the IAsyncResult interface of the asynchronous method that has just completed. Remember that the IAsyncResult interface object is inside the AsyncResult class object.
  • Although the IAsyncResult interface doesn't have a reference to the delegate object, the AsyncResult class object enclosing it does have a reference to the delegate object. So, the first line inside the example method body gets a reference to the class object by casting the interface reference to the class type. Variable ar now has a reference to the class object.
  • With the reference to the class object, you can now use the AsyncDelegate property of the class object and cast it to the appropriate delegate type. This gives you the delegate reference, which you can then use to call EndInvoke.
using System.Runtime.Remoting.Messaging;     // Contains AsyncResult class

   void CallWhenDone( IAsyncResult iar )
   {
      AsyncResult ar = (AsyncResult) iar;       // Get class object reference
      MyDel del = (MyDel) ar.AsyncDelegate;     // Get reference to delegate

      long Sum = del.EndInvoke( iar );          // Call EndInvoke
         ...
   }
Image

Figure 22-7. Extracting the delegate's reference inside the callback method

The following code puts it all together and is an example of using the callback pattern.

using System;
   using System.Runtime.Remoting.Messaging;      // To access the AsyncResult type
   using System.Threading;

   delegate long MyDel(int first, int second);
  
   class Program
   {
      static long Sum(int x, int y)
      {
         Console.WriteLine("                         Inside Sum");
         Thread.Sleep(100);
         return x + y;
      }
  
      static void CallWhenDone(IAsyncResult iar)
      {
         Console.WriteLine("                         Inside CallWhenDone.");
         AsyncResult ar = (AsyncResult) iar;        
         MyDel del = (MyDel)ar.AsyncDelegate;
  
         long result = del.EndInvoke(iar);
         Console.WriteLine
            ("                         The result is: {0}.", result);
      }
  
      static void Main()
      {
         MyDel del = new MyDel(Sum);
  
         Console.WriteLine("Before BeginInvoke");
         IAsyncResult iar =
            del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);
  
         Console.WriteLine("Doing more work in Main.");
         Thread.Sleep(500);
         Console.WriteLine("Done with Main. Exiting.");
      }
   }

This code produces the following output:


Before BeginInvoke
Doing more work in Main.
                         Inside Sum
                         Inside CallWhenDone.
                         The result is: 8.
Done with Main. Exiting.

Timers

Timers provide another way to run an asynchronous method on a regular, recurring basis. Although there are several Timer classes available in the .NET BCL, I'll describe the one in the System.Threading namespace.

The important things to know about this timer class are the following:

  • The timer uses a callback method that is called each time the timer expires. The callback method must be in the form of the TimerCallback delegate, which has the following form. It takes a single parameter of type object and has a void return type.
void TimerCallback( object state )
  • When the timer expires, the system sets up the callback method on a thread from the thread pool, supplies the state object as its parameter, and starts it running.
  • You can set a number of the timer's characteristics, including the following:
    • The dueTime is the amount of time before the first call of the callback method. If dueTime is set to the special value Timeout.Infinite, the timer will not start. If it's set to 0, the callback is called immediately.
    • The period is the amount of time between each successive call of the callback method. If it's value is set to Timeout.Infinite, the callback won't be called after the first time.
    • The state is either null or a reference to an object to be passed to the callback method each time it's executed.

The constructor for the Timer class takes as parameters the name of the callback method, the dueTime, the period, and the state. There are several constructors for Timer; the one that's probably the most commonly used has the following form:

Timer( TimerCallback callback, object state, uint dueTime, uint period)

The following code statement shows an example of the creation of a Timer object:

Image

Once a Timer object is created, you can change its dueTime or period using the Change method.

The following code shows an example of using a timer. The Main method creates the timer so that it will call the callback for the first time after two seconds and once every second after that. The callback method simply prints out a message, including the number of times it's been called.

Image

This code produces the following output before being terminated after about five seconds:


Timer started.
Processing timer event 1
Processing timer event 2
Processing timer event 3
Processing timer event 4

There are several other timer classes supplied by the .NET BCL, each having its own uses. The other timer classes are the following:

  • System.Windows.Forms.Timer: This class is used in Windows Forms applications to periodically place WM_TIMER messages into the program's message queue. When the program gets the message from the queue, it processes the handler synchronously on the main user interface thread. This is extremely important in Windows Forms applications.
  • System.Timers.Timer: This class is more extensive and contains a number of members for manipulating the timer through properties and methods. It also has a member event called Elapsed, which is raised when each period expires. This timer can run on either a user interface thread or a worker thread.
..................Content has been hidden....................

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