This walkthrough illustrates how to create a component that implements the Event-based Asynchronous Pattern. It is implemented using helper classes from the System.ComponentModel namespace, which ensures that the component works correctly under any application model, including ASP.NET, Console applications and Windows Forms applications. This component is also designable with a PropertyGrid control and your own custom designers.

When you are through, you will have an application that computes prime numbers asynchronously. Your application will have a main user interface (UI) thread and a thread for each prime number calculation. Although testing whether a large number is prime can take a noticeable amount of time, the main UI thread will not be interrupted by this delay, and the form will be responsive during the calculations. You will be able to run as many calculations as you like concurrently and selectively cancel pending calculations.

To create the component

Your component communicates to clients using events. The MethodNameCompleted event alerts clients to the completion of an asynchronous task, and the MethodNameProgressChanged event informs clients of the progress of an asynchronous task.

To define asynchronous events for clients of your component:

using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Threading;
using System.Windows.Forms;

Before the PrimeNumberCalculator class definition, declare delegates for progress and completion events.

After the PrimeNumberCalculator class definition, derive the CalculatePrimeCompletedEventArgs class for reporting the outcome of each calculation to the client's event handler for the CalculatePrimeCompleted.event. In addition to the AsyncCompletedEventArgs properties, this class enables the client to determine what number was tested, whether it is prime, and what the first divisor is if it is not prime.

To test your component

warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used
warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used

The asynchronous aspects of the PrimeNumberCalculator component are implemented internally with a special delegate known as a SendOrPostCallback. A SendOrPostCallback represents a callback method that executes on a ThreadPool thread. The callback method must have a signature that takes a single parameter of type Object, which means you will need to pass state among delegates in a wrapper class. For more information, see SendOrPostCallback.

Declare a delegate in the PrimeNumberCalculator class that handles the actual work to be done asynchronously. This delegate wraps the worker method that tests whether a number is prime. The delegate takes an AsyncOperation parameter, which will be used to track the lifetime of the asynchronous operation.

Create a collection for managing lifetimes of pending asynchronous operations. The client needs a way to track operations as they are executed and completed, and this tracking is done by requiring the client to pass a unique token, or task ID, when the client makes the call to the asynchronous method. The PrimeNumberCalculator component must keep track of each call by associating the task ID with its corresponding invocation. If the client passes a task ID that is not unique, the PrimeNumberCalculator component must raise an exception.

The PrimeNumberCalculator component keeps track of task ID by using a special collection class called a HybridDictionary. In the class definition, create a HybridDictionary called userTokenToLifetime.

The completion delegate is the method that the underlying, free-threaded asynchronous behavior will invoke when the asynchronous operation ends by successful completion, error, or cancellation. This invocation happens on an arbitrary thread.

This method is where the client's task ID is removed from the internal collection of unique client tokens. This method also ends the lifetime of a particular asynchronous operation by calling the PostOperationCompleted method on the corresponding AsyncOperation. This call raises the completion event on the thread that is appropriate for the application model. After the PostOperationCompleted method is called, this instance of AsyncOperation can no longer be used, and any subsequent attempts to use it will throw an exception.

The CompletionMethod signature must hold all state necessary to describe the outcome of the asynchronous operation. It holds state for the number that was tested by this particular asynchronous operation, whether the number is prime, and the value of its first divisor if it is not a prime number. It also holds state describing any exception that occurred, and the AsyncOperation corresponding to this particular task.

To complete an asynchronous operation:

Implement the completion method. It takes six parameters, which it uses to populate a CalculatePrimeCompletedEventArgs that is returned to the client through the client's CalculatePrimeCompletedEventHandler. It removes the client's task ID token from the internal collection, and it ends the asynchronous operation's lifetime with a call to PostOperationCompleted. The AsyncOperation marshals the call to the thread or context that is appropriate for the application model.

// This is the method that the underlying, free-threaded // asynchronous behavior will invoke. This will happen on // an arbitrary thread. privatevoid CompletionMethod(
int numberToTest,
int firstDivisor,
bool isPrime,
Exception exception,
bool canceled,
AsyncOperation asyncOp )
{
// If the task was not previously canceled, // remove the task from the lifetime collection. if (!canceled)
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(asyncOp.UserSuppliedState);
}
}
// Package the results of the operation in a // CalculatePrimeCompletedEventArgs.
CalculatePrimeCompletedEventArgs e =
new CalculatePrimeCompletedEventArgs(
numberToTest,
firstDivisor,
isPrime,
exception,
canceled,
asyncOp.UserSuppliedState);
// End the task. The asyncOp object is responsible // for marshaling the call.
asyncOp.PostOperationCompleted(onCompletedDelegate, e);
// Note that after the call to OperationCompleted, // asyncOp is no longer usable, and any attempt to use it // will cause an exception to be thrown.
}

So far, you have implemented the supporting asynchronous code for the PrimeNumberCalculator component. Now you can implement the code that does the actual work. You will implement three methods: CalculateWorker, BuildPrimeNumberList, and IsPrime. Together, BuildPrimeNumberList and IsPrime comprise a well-known algorithm called the Sieve of Eratosthenes, which determines if a number is prime by finding all the prime numbers up to the square root of the test number. If no divisors are found by that point, the test number is prime.

If this component were written for maximum efficiency, it would remember all the prime numbers discovered by various invocations for different test numbers. It would also check for trivial divisors like 2, 3, and 5. The intent of this example is to demonstrate how time-consuming operations can be executed asynchronously, however, so these optimizations are left as an exercise for you.

The CalculateWorker method is wrapped in a delegate and is invoked asynchronously with a call to BeginInvoke.

Note

Progress reporting is implemented in the BuildPrimeNumberList method. On fast computers, ProgressChanged events can be raised in rapid succession. The client thread, on which these events are raised, must be able to handle this situation. User interface code may be flooded with messages and unable to keep up, resulting in hanging behavior. For an example user interface that handles this situation, see How to: Implement a Client of the Event-based Asynchronous Pattern.

To execute the prime number calculation asynchronously:

Implement the TaskCanceled utility method. This checks the task lifetime collection for the given task ID, and returns true if the task ID is not found.

Implement BuildPrimeNumberList. It takes two parameters: the number to test, and an AsyncOperation. It uses the AsyncOperation to report progress and incremental results. This assures that the client's event handlers are called on the proper thread or context for the application model. When BuildPrimeNumberList finds a prime number, it reports this as an incremental result to the client's event handler for the ProgressChanged event. This requires a class derived from ProgressChangedEventArgs, called CalculatePrimeProgressChangedEventArgs, which has one added property called LatestPrimeNumber.

The BuildPrimeNumberList method also periodically calls the TaskCanceled method and exits if the method returns true.

Implement IsPrime. It takes three parameters: a list of known prime numbers, the number to test, and an output parameter for the first divisor found. Given the list of prime numbers, it determines if the test number is prime.

Derive CalculatePrimeProgressChangedEventArgs from ProgressChangedEventArgs. This class is necessary for reporting incremental results to the client's event handler for the ProgressChanged event. It has one added property called LatestPrimeNumber.

To test your component

You start the worker method on its own thread by calling BeginInvoke on the delegate that wraps it. To manage the lifetime of a particular asynchronous operation, you call the CreateOperation method on the AsyncOperationManager helper class. This returns an AsyncOperation, which marshals calls on the client's event handlers to the proper thread or context.

To implement Start and Cancel functionality:

Implement the CalculatePrimeAsync method. Make sure the client-supplied token (task ID) is unique with respect to all the tokens representing currently pending tasks. If the client passes in a non-unique token, CalculatePrimeAsync raises an exception. Otherwise, the token is added to the task ID collection.

Implement the CancelAsync method. If the taskId parameter exists in the token collection, it is removed. This prevents canceled tasks that have not started from running. If the task is running, the BuildPrimeNumberList method exits when it detects that the task ID has been removed from the lifetime collection.

You can fill out this example by writing CalculatePrime, the synchronous equivalent of CalculatePrimeAsync method. This will make the PrimeNumberCalculator component fully compliant with the Event-based Asynchronous Pattern.

You can improve this example by retaining the list of all the prime numbers discovered by various invocations for different test numbers. Using this approach, each task will benefit from the work done by previous tasks. Be careful to protect this list with lock regions, so access to the list by different threads is serialized.

You can also improve this example by testing for trivial divisors, like 2, 3, and 5.