Walkthrough: Implementing a Component That Supports the Event-based Asynchronous PatternĀ
If you are writing a class with some operations that may incur noticeable delays, consider giving it asynchronous functionality by implementing the Event-based Asynchronous Pattern Overview.
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.
Tasks illustrated in this walkthrough include:
Creating the Component
Defining Public Asynchronous Events and Delegates
Defining Private Delegates
Implementing Public Events
Implementing the Completion Method
Implementing the Worker Methods
Implementing Start and Cancel Methods
To copy the code in this topic as a single listing, see How to: Implement a Component That Supports the Event-based Asynchronous Pattern.
Creating the Component
The first step is to create the component that will implement the Event-based Asynchronous Pattern.
To create the component
- Create a class called
PrimeNumberCalculator
that inherits from Component.
Defining Public Asynchronous Events and Delegates
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:
Import the System.Threading and System.Collections.Specialized namespaces at the top of your file.
Imports System Imports System.Collections Imports System.Collections.Specialized Imports System.ComponentModel Imports System.Drawing Imports System.Globalization Imports System.Threading Imports System.Windows.Forms
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;
import System.*; import System.Collections.*; import System.Collections.Specialized.*; import System.ComponentModel.*; import System.Data.*; import System.Drawing.*; import System.Threading.*; import System.Windows.Forms.*;
Before the
PrimeNumberCalculator
class definition, declare delegates for progress and completion events.Public Delegate Sub ProgressChangedEventHandler( _ ByVal e As ProgressChangedEventArgs) Public Delegate Sub CalculatePrimeCompletedEventHandler( _ ByVal sender As Object, _ ByVal e As CalculatePrimeCompletedEventArgs)
public delegate void ProgressChangedEventHandler( ProgressChangedEventArgs e); public delegate void CalculatePrimeCompletedEventHandler( object sender, CalculatePrimeCompletedEventArgs e);
public delegate void ProgressChangedEventHandler(ProgressChangedEventArgs e); /** @delegate */ public delegate void CalculatePrimeCompletedEventHandler(Object sender, CalculatePrimeCompletedEventArgs e);
In the
PrimeNumberCalculator
class definition, declare events for reporting progress and completion to clients.Public Event ProgressChanged _ As ProgressChangedEventHandler Public Event CalculatePrimeCompleted _ As CalculatePrimeCompletedEventHandler
public event ProgressChangedEventHandler ProgressChanged; public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
public ProgressChangedEventHandler progressChanged = null; /** @event */ public void add_ProgressChanged(ProgressChangedEventHandler p) { progressChanged =(ProgressChangedEventHandler)System.Delegate. Combine(progressChanged, p); } /** @event */ public void remove_ProgressChanged(ProgressChangedEventHandler p) { progressChanged =(ProgressChangedEventHandler)System.Delegate. Remove(progressChanged, p); } public CalculatePrimeCompletedEventHandler calculatePrimeCompleted = null; /** @event */ public void add_CalculatePrimeCompleted(CalculatePrimeCompletedEventHandler p) { calculatePrimeCompleted =(CalculatePrimeCompletedEventHandler) System.Delegate.Combine(calculatePrimeCompleted, p); } /** @event */ public void remove_CalculatePrimeCompleted( CalculatePrimeCompletedEventHandler p) { calculatePrimeCompleted =(CalculatePrimeCompletedEventHandler) System.Delegate.Remove(calculatePrimeCompleted, p); }
After the
PrimeNumberCalculator
class definition, derive theCalculatePrimeCompletedEventArgs
class for reporting the outcome of each calculation to the client's event handler for theCalculatePrimeCompleted
.event. In addition to theAsyncCompletedEventArgs
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.Public Class CalculatePrimeCompletedEventArgs Inherits AsyncCompletedEventArgs Private numberToTestValue As Integer = 0 Private firstDivisorValue As Integer = 1 Private isPrimeValue As Boolean Public Sub New( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal isPrime As Boolean, _ ByVal e As Exception, _ ByVal canceled As Boolean, _ ByVal state As Object) MyBase.New(e, canceled, state) Me.numberToTestValue = numberToTest Me.firstDivisorValue = firstDivisor Me.isPrimeValue = isPrime End Sub Public ReadOnly Property NumberToTest() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return numberToTestValue End Get End Property Public ReadOnly Property FirstDivisor() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return firstDivisorValue End Get End Property Public ReadOnly Property IsPrime() As Boolean Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return isPrimeValue End Get End Property End Class
public class CalculatePrimeCompletedEventArgs : AsyncCompletedEventArgs { private int numberToTestValue = 0; private int firstDivisorValue = 1; private bool isPrimeValue; public CalculatePrimeCompletedEventArgs( int numberToTest, int firstDivisor, bool isPrime, Exception e, bool canceled, object state) : base(e, canceled, state) { this.numberToTestValue = numberToTest; this.firstDivisorValue = firstDivisor; this.isPrimeValue = isPrime; } public int NumberToTest { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return numberToTestValue; } } public int FirstDivisor { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return firstDivisorValue; } } public bool IsPrime { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return isPrimeValue; } } }
public class CalculatePrimeCompletedEventArgs extends AsyncCompletedEventArgs { private int numberToTestValue = 0; private int firstDivisorValue = 1; private boolean isPrimeValue; public CalculatePrimeCompletedEventArgs(int numberToTest, int firstDivisor, boolean isPrime, System.Exception e, boolean cancelled, Object state) { super(e, cancelled, state); this.numberToTestValue = numberToTest; this.firstDivisorValue = firstDivisor; this.isPrimeValue = isPrime; } /** @property */ public int get_NumberToTest() { // Raise an exception if the operation failed or // was cancelled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return numberToTestValue; } /** @property */ public int get_FirstDivisor() { // Raise an exception if the operation failed or // was cancelled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return firstDivisorValue; } /** @property */ public boolean get_IsPrime() { // Raise an exception if the operation failed or // was cancelled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return isPrimeValue; } }
Checkpoint
At this point, you can build the component.
To test your component
Compile the component.
You will receive two compiler warnings:
warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used
These warnings will be cleared in the next section.
Defining Private Delegates
The asynchronous aspects of thePrimeNumberCalculator
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.
To implement your component's internal asynchronous behavior:
Declare and create the SendOrPostCallback delegates in the
PrimeNumberCalculator
class. Create the SendOrPostCallback objects in a utility method calledInitializeDelegates
.You will need two delegates: one for reporting progress to the client, and one for reporting completion to the client.
Private onProgressReportDelegate As SendOrPostCallback Private onCompletedDelegate As SendOrPostCallback ... Protected Overridable Sub InitializeDelegates() onProgressReportDelegate = _ New SendOrPostCallback(AddressOf ReportProgress) onCompletedDelegate = _ New SendOrPostCallback(AddressOf CalculateCompleted) End Sub
private SendOrPostCallback onProgressReportDelegate; private SendOrPostCallback onCompletedDelegate; ... protected virtual void InitializeDelegates() { onProgressReportDelegate = new SendOrPostCallback(ReportProgress); onCompletedDelegate = new SendOrPostCallback(CalculateCompleted); }
private SendOrPostCallback onProgressReportDelegate; private SendOrPostCallback onCompletedDelegate; ... protected void InitializeDelegates() { onProgressReportDelegate = new SendOrPostCallback(ReportProgress); onCompletedDelegate = new SendOrPostCallback(CalculateCompleted); }
Call the
InitializeDelegates
method in your component's constructor.Public Sub New() InitializeComponent() InitializeDelegates() End Sub
public PrimeNumberCalculator() { InitializeComponent(); InitializeDelegates(); }
public PrimeNumberCalculator() { /// /// Required for Windows.Forms Class Composition Designer support /// InitializeComponent(); InitializeDelegates(); }
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.Private Delegate Sub WorkerEventHandler( _ ByVal numberToCheck As Integer, _ ByVal asyncOp As AsyncOperation)
private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp);
private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp );
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, thePrimeNumberCalculator
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 calleduserTokenToLifetime
.Private userStateToLifetime As New HybridDictionary()
private HybridDictionary userStateToLifetime = new HybridDictionary();
private HybridDictionary userStateToLifetime = new HybridDictionary();
Implementing Public Events
Components that implement the Event-based Asynchronous Pattern communicate to clients using events. These events are invoked on the proper thread with the help of the AsyncOperation class.
To raise events to your component's clients:
Implement public events for reporting to clients. You will need an event for reporting progress and one for reporting completion.
' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub CalculateCompleted(ByVal operationState As Object) Dim e As CalculatePrimeCompletedEventArgs = operationState OnCalculatePrimeCompleted(e) End Sub ' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub ReportProgress(ByVal state As Object) Dim e As ProgressChangedEventArgs = state OnProgressChanged(e) End Sub Protected Sub OnCalculatePrimeCompleted( _ ByVal e As CalculatePrimeCompletedEventArgs) RaiseEvent CalculatePrimeCompleted(Me, e) End Sub Protected Sub OnProgressChanged( _ ByVal e As ProgressChangedEventArgs) RaiseEvent ProgressChanged(e) End Sub
// This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void CalculateCompleted(object operationState) { CalculatePrimeCompletedEventArgs e = operationState as CalculatePrimeCompletedEventArgs; OnCalculatePrimeCompleted(e); } // This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void ReportProgress(object state) { ProgressChangedEventArgs e = state as ProgressChangedEventArgs; OnProgressChanged(e); } protected void OnCalculatePrimeCompleted( CalculatePrimeCompletedEventArgs e) { if (CalculatePrimeCompleted != null) { CalculatePrimeCompleted(this, e); } } protected void OnProgressChanged(ProgressChangedEventArgs e) { if (ProgressChanged != null) { ProgressChanged(e); } }
// You are guaranteed to be on the correct thread, as // this method is invoked via the AsyncOperation object. private void CalculateCompleted(Object operationState) { CalculatePrimeCompletedEventArgs e = (CalculatePrimeCompletedEventArgs)operationState; OnCalculatePrimeCompleted(e); } // You are guaranteed to be on the correct thread, as // this method is invoked via the AsyncOperation object. private void ReportProgress(Object state) { ProgressChangedEventArgs e = (ProgressChangedEventArgs)state; OnProgressChanged(e); } protected void OnCalculatePrimeCompleted(CalculatePrimeCompletedEventArgs e) { if (calculatePrimeCompleted != null) { calculatePrimeCompleted.Invoke(this, e); } } protected void OnProgressChanged(ProgressChangedEventArgs e) { if (progressChanged != null) { progressChanged.Invoke(e); } }
Implementing the Completion Method
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'sCalculatePrimeCompletedEventHandler
. 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. Private Sub CompletionMethod( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal prime As Boolean, _ ByVal exc As Exception, _ ByVal canceled As Boolean, _ ByVal asyncOp As AsyncOperation) ' If the task was not previously canceled, ' remove the task from the lifetime collection. If Not canceled Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(asyncOp.UserSuppliedState) End SyncLock End If ' Package the results of the operation in a ' CalculatePrimeCompletedEventArgs. Dim e As New CalculatePrimeCompletedEventArgs( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ 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 PostOperationCompleted, asyncOp ' is no longer usable, and any attempt to use it will cause. ' an exception to be thrown. End Sub
// This is the method that the underlying, free-threaded // asynchronous behavior will invoke. This will happen on // an arbitrary thread. private void 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. }
// This is the method that the underlying, free-threaded // asynchronous behavior will invoke. This will happen on // an arbitrary thread. private void CompletionMethod( int numberToTest, int firstDivisor, boolean isPrime, System.Exception exception, boolean canceled, AsyncOperation asyncOp) { // If the task was not previously canceled, // remove the task from the lifetime collection. if (!canceled) { synchronized (userStateToLifetime.get_SyncRoot()) { userStateToLifetime.Remove(asyncOp.get_UserSuppliedState()); } } // Package the results of the operation in a // CalculatePrimeCompletedEventArgs. CalculatePrimeCompletedEventArgs e = new CalculatePrimeCompletedEventArgs( numberToTest, firstDivisor, isPrime, exception, canceled, asyncOp.get_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. } // 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.
Checkpoint
At this point, you can build the component.
To test your component
Compile the component.
You will receive one compiler warning:
warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used
This warning will be resolved in the next section.
Implementing the Worker Methods
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 |
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.' Utility method for determining if a ' task has been canceled. Private Function TaskCanceled(ByVal taskId As Object) As Boolean Return (userStateToLifetime(taskId) Is Nothing) End Function
// Utility method for determining if a // task has been canceled. private bool TaskCanceled(object taskId) { return( userStateToLifetime[taskId] == null ); }
// Utility method for determining if a // task has been canceled. private boolean TaskCanceled(Object taskId) { return (!userStateToLifetime.Contains(taskId) ); }
Implement the
CalculateWorker
method. It takes two parameters: a number to test, and an AsyncOperation.' This method performs the actual prime number computation. ' It is executed on the worker thread. Private Sub CalculateWorker( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) Dim prime As Boolean = False Dim firstDivisor As Integer = 1 Dim exc As Exception = Nothing ' Check that the task is still active. ' The operation may have been canceled before ' the thread was scheduled. If Not Me.TaskCanceled(asyncOp.UserSuppliedState) Then Try ' Find all the prime numbers up to the ' square root of numberToTest. Dim primes As ArrayList = BuildPrimeNumberList( _ numberToTest, asyncOp) ' Now we have a list of primes less than 'numberToTest. prime = IsPrime( _ primes, _ numberToTest, _ firstDivisor) Catch ex As Exception exc = ex End Try End If Me.CompletionMethod( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ TaskCanceled(asyncOp.UserSuppliedState), _ asyncOp) End Sub
// This method performs the actual prime number computation. // It is executed on the worker thread. private void CalculateWorker( int numberToTest, AsyncOperation asyncOp) { bool isPrime = false; int firstDivisor = 1; Exception e = null; // Check that the task is still active. // The operation may have been canceled before // the thread was scheduled. if (!TaskCanceled(asyncOp.UserSuppliedState)) { try { // Find all the prime numbers up to // the square root of numberToTest. ArrayList primes = BuildPrimeNumberList( numberToTest, asyncOp); // Now we have a list of primes less than // numberToTest. isPrime = IsPrime( primes, numberToTest, out firstDivisor); } catch (Exception ex) { e = ex; } } //CalculatePrimeState calcState = new CalculatePrimeState( // numberToTest, // firstDivisor, // isPrime, // e, // TaskCanceled(asyncOp.UserSuppliedState), // asyncOp); //this.CompletionMethod(calcState); this.CompletionMethod( numberToTest, firstDivisor, isPrime, e, TaskCanceled(asyncOp.UserSuppliedState), asyncOp); //completionMethodDelegate(calcState); }
private void CalculateWorker( int numberToTest, AsyncOperation asyncOp) { boolean isPrime = false; int firstDivisor = 1; System.Exception exception = null; // Check that the task is still active. // The operation may have been canceled before // the thread was scheduled. if (!TaskCanceled(asyncOp.get_UserSuppliedState())) { try { // Find all the prime numbers up to // the square root of numberToTest. ArrayList primes = BuildPrimeNumberList( numberToTest, asyncOp); // Now we have a list of primes less than // numberToTest. isPrime = IsPrime( primes, numberToTest, /** @out */ firstDivisor); } catch (System.Exception ex) { exception = ex; } } this.CompletionMethod( numberToTest, firstDivisor, isPrime, exception, TaskCanceled(asyncOp.get_UserSuppliedState()), asyncOp); }
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. WhenBuildPrimeNumberList
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, calledCalculatePrimeProgressChangedEventArgs
, which has one added property calledLatestPrimeNumber
.The
BuildPrimeNumberList
method also periodically calls theTaskCanceled
method and exits if the method returns true.' This method computes the list of prime numbers used by the ' IsPrime method. Private Function BuildPrimeNumberList( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) As ArrayList Dim e As ProgressChangedEventArgs = Nothing Dim primes As New ArrayList Dim firstDivisor As Integer Dim n As Integer = 5 ' Add the first prime numbers. primes.Add(2) primes.Add(3) ' Do the work. While n < numberToTest And _ Not Me.TaskCanceled(asyncOp.UserSuppliedState) If IsPrime(primes, n, firstDivisor) Then ' Report to the client that you found a prime. e = New CalculatePrimeProgressChangedEventArgs( _ n, _ CSng(n) / CSng(numberToTest) * 100, _ asyncOp.UserSuppliedState) asyncOp.Post(Me.onProgressReportDelegate, e) primes.Add(n) ' Yield the rest of this time slice. Thread.Sleep(0) End If ' Skip even numbers. n += 2 End While Return primes End Function
// This method computes the list of prime numbers used by the // IsPrime method. private ArrayList BuildPrimeNumberList( int numberToTest, AsyncOperation asyncOp) { ProgressChangedEventArgs e = null; ArrayList primes = new ArrayList(); int firstDivisor; int n = 5; // Add the first prime numbers. primes.Add(2); primes.Add(3); // Do the work. while (n < numberToTest && !TaskCanceled( asyncOp.UserSuppliedState ) ) { if (IsPrime(primes, n, out firstDivisor)) { // Report to the client that a prime was found. e = new CalculatePrimeProgressChangedEventArgs( n, (int)((float)n / (float)numberToTest * 100), asyncOp.UserSuppliedState); asyncOp.Post(this.onProgressReportDelegate, e); primes.Add(n); // Yield the rest of this time slice. Thread.Sleep(0); } // Skip even numbers. n += 2; } return primes; }
// This method computes the list of prime numbers used by the // IsPrime method. private ArrayList BuildPrimeNumberList(int numberToTest, AsyncOperation asyncOp) { ProgressChangedEventArgs e = null; ArrayList primes = new ArrayList(); int firstDivisor = 1; int n = 5; // Add the first prime numbers. primes.Add((Int32)2); primes.Add((Int32)3); // Do the work. while (n < numberToTest && !TaskCanceled( asyncOp.get_UserSuppliedState()) ) { if (IsPrime(primes, n, /** @out */firstDivisor)) { // Report to the client that you found a prime. e = new CalculatePrimeProgressChangedEventArgs(n, (int)((float)n / ( float)(numberToTest) * 100), asyncOp.get_UserSuppliedState()); asyncOp.Post(this.onProgressReportDelegate, e); primes.Add((Int32)n); // Yield the rest of this time slice. System.Threading.Thread.Sleep(0); } // Skip even numbers. n += 2; } return primes; }
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.' This method tests n for primality against the list of ' prime numbers contained in the primes parameter. Private Function IsPrime( _ ByVal primes As ArrayList, _ ByVal n As Integer, _ ByRef firstDivisor As Integer) As Boolean Dim foundDivisor As Boolean = False Dim exceedsSquareRoot As Boolean = False Dim i As Integer = 0 Dim divisor As Integer = 0 firstDivisor = 1 ' Stop the search if: ' there are no more primes in the list, ' there is a divisor of n in the list, or ' there is a prime that is larger than ' the square root of n. While i < primes.Count AndAlso _ Not foundDivisor AndAlso _ Not exceedsSquareRoot ' The divisor variable will be the smallest prime number ' not yet tried. divisor = primes(i) i = i + 1 ' Determine whether the divisor is greater than the ' square root of n. If divisor * divisor > n Then exceedsSquareRoot = True ' Determine whether the divisor is a factor of n. ElseIf n Mod divisor = 0 Then firstDivisor = divisor foundDivisor = True End If End While Return Not foundDivisor End Function
// This method tests n for primality against the list of // prime numbers contained in the primes parameter. private bool IsPrime( ArrayList primes, int n, out int firstDivisor) { bool foundDivisor = false; bool exceedsSquareRoot = false; int i = 0; int divisor = 0; firstDivisor = 1; // Stop the search if: // there are no more primes in the list, // there is a divisor of n in the list, or // there is a prime that is larger than // the square root of n. while ( (i < primes.Count) && !foundDivisor && !exceedsSquareRoot) { // The divisor variable will be the smallest // prime number not yet tried. divisor = (int)primes[i++]; // Determine whether the divisor is greater // than the square root of n. if (divisor * divisor > n) { exceedsSquareRoot = true; } // Determine whether the divisor is a factor of n. else if (n % divisor == 0) { firstDivisor = divisor; foundDivisor = true; } } return !foundDivisor; }
// This method tests n for primality against the list of // prime numbers contained in the primes parameter. private boolean IsPrime( ArrayList primes, int n, /** @ref */int firstDivisor) { boolean foundDivisor = false; boolean exceedsSquareRoot = false; int i = 0; int divisor = 0; firstDivisor = 1; // Stop the search if: // there are no more primes in the list, // there is a divisor of n in the list, or // there is a prime that is larger than // the square root of n. while (i < primes.get_Count() && !foundDivisor && !exceedsSquareRoot) { // The divisor variable will be the smallest // prime number not yet tried. divisor = Convert.ToInt32(primes.get_Item(i++)); // Determine whether the divisor is greater // than the square root of n. if (divisor * divisor > n) { exceedsSquareRoot = true; } // Determine whether the divisor is a factor of n. else { if (n % divisor == 0) { firstDivisor = divisor; foundDivisor = true; } } } return !foundDivisor; }
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 calledLatestPrimeNumber
.Public Class CalculatePrimeProgressChangedEventArgs Inherits ProgressChangedEventArgs Private latestPrimeNumberValue As Integer = 1 Public Sub New( _ ByVal latestPrime As Integer, _ ByVal progressPercentage As Integer, _ ByVal UserState As Object) MyBase.New(progressPercentage, UserState) Me.latestPrimeNumberValue = latestPrime End Sub Public ReadOnly Property LatestPrimeNumber() As Integer Get Return latestPrimeNumberValue End Get End Property End Class
public class CalculatePrimeProgressChangedEventArgs : ProgressChangedEventArgs { private int latestPrimeNumberValue = 1; public CalculatePrimeProgressChangedEventArgs( int latestPrime, int progressPercentage, object userToken) : base( progressPercentage, userToken ) { this.latestPrimeNumberValue = latestPrime; } public int LatestPrimeNumber { get { return latestPrimeNumberValue; } } }
public class CalculatePrimeProgressChangedEventArgs extends ProgressChangedEventArgs { private int latestPrimeNumberValue = 1; public CalculatePrimeProgressChangedEventArgs(int latestPrime, int progressPercentage, Object userToken) { super(progressPercentage, userToken); this.latestPrimeNumberValue = latestPrime; } /** @property */ public int get_LatestPrimeNumber() { return latestPrimeNumberValue; } }
Checkpoint
At this point, you can build the component.
To test your component
Compile the component.
All that remains to be written are the methods to start and cancel asynchronous operations,
CalculatePrimeAsync
andCancelAsync
.
Implementing the Start and Cancel Methods
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.
You cancel a particular pending operation by calling PostOperationCompleted on its corresponding AsyncOperation. This ends that operation, and any subsequent calls to its AsyncOperation will throw an exception.
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.' This method starts an asynchronous calculation. ' First, it checks the supplied task ID for uniqueness. ' If taskId is unique, it creates a new WorkerEventHandler ' and calls its BeginInvoke method to start the calculation. Public Overridable Sub CalculatePrimeAsync( _ ByVal numberToTest As Integer, _ ByVal taskId As Object) ' Create an AsyncOperation for taskId. Dim asyncOp As AsyncOperation = _ AsyncOperationManager.CreateOperation(taskId) ' Multiple threads will access the task dictionary, ' so it must be locked to serialize access. SyncLock userStateToLifetime.SyncRoot If userStateToLifetime.Contains(taskId) Then Throw New ArgumentException( _ "Task ID parameter must be unique", _ "taskId") End If userStateToLifetime(taskId) = asyncOp End SyncLock ' Start the asynchronous operation. Dim workerDelegate As New WorkerEventHandler( _ AddressOf CalculateWorker) workerDelegate.BeginInvoke( _ numberToTest, _ asyncOp, _ Nothing, _ Nothing) End Sub
// This method starts an asynchronous calculation. // First, it checks the supplied task ID for uniqueness. // If taskId is unique, it creates a new WorkerEventHandler // and calls its BeginInvoke method to start the calculation. public virtual void CalculatePrimeAsync( int numberToTest, object taskId) { // Create an AsyncOperation for taskId. AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId); // Multiple threads will access the task dictionary, // so it must be locked to serialize access. lock (userStateToLifetime.SyncRoot) { if (userStateToLifetime.Contains(taskId)) { throw new ArgumentException( "Task ID parameter must be unique", "taskId"); } userStateToLifetime[taskId] = asyncOp; } // Start the asynchronous operation. WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker); workerDelegate.BeginInvoke( numberToTest, asyncOp, null, null); }
// This method starts an asynchronous calculation. // First, it checks the supplied task ID for uniqueness. // If taskId is unique, it creates a new WorkerEventHandler // and calls its BeginInvoke method to start the calculation. public void CalculatePrimeAsync(int numberToTest, Object taskId) { // Create an AsyncOperation for taskId. AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId); synchronized (userStateToLifetime.get_SyncRoot()) { // Multiple threads will access the task dictionary, // so it must be locked to serialize access. if (userStateToLifetime.Contains(taskId)) { throw new ArgumentException("Task ID parameter must be unique", "taskId"); } userStateToLifetime.set_Item(taskId, asyncOp); } // Start the asynchronous operation. WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker); workerDelegate.BeginInvoke( numberToTest, asyncOp, null, null); }
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, theBuildPrimeNumberList
method exits when it detects that the task ID has been removed from the lifetime collection.' This method cancels a pending asynchronous operation. Public Sub CancelAsync(ByVal taskId As Object) Dim obj As Object = userStateToLifetime(taskId) If (obj IsNot Nothing) Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(taskId) End SyncLock End If End Sub
// This method cancels a pending asynchronous operation. public void CancelAsync(object taskId) { AsyncOperation asyncOp = userStateToLifetime[taskId] as AsyncOperation; if (asyncOp != null) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(taskId); } } }
// This method cancels a pending asynchronous operation. public void CancelAsync(Object taskId) { Object obj = userStateToLifetime.get_Item(taskId); if (obj != null) { synchronized (userStateToLifetime.get_SyncRoot()) { userStateToLifetime.Remove(taskId); } } }
Checkpoint
At this point, you can build the component.
To test your component
- Compile the component.
ThePrimeNumberCalculator
component is now complete and ready to use.
For an example client that uses the PrimeNumberCalculator
component, see How to: Implement a Client of the Event-based Asynchronous Pattern.
Next Steps
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.
See Also
Tasks
How to: Run an Operation in the Background
How to: Implement a Component That Supports the Event-based Asynchronous Pattern
Concepts
Event-based Asynchronous Pattern Overview
Other Resources
Multithreading in Visual Basic
Multithreaded Programming with the Event-based Asynchronous Pattern