How to: Implement a Component That Supports the Event-based Asynchronous Pattern
The following code example implements a component with an asynchronous method, according to the Event-based Asynchronous Pattern Overview. The component is a prime number calculator that uses the Sieve of Eratosthenes algorithm to determine if a number is prime or composite.
There is extensive support for this task in Visual Studio. For more information, see Walkthrough: Implementing a Component That Supports the Event-based Asynchronous Pattern.
For an example client that uses the PrimeNumberCalculator component, see How to: Implement a Client of the Event-based Asynchronous Pattern.
Example
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
...
Public Delegate Sub ProgressChangedEventHandler( _
ByVal e As ProgressChangedEventArgs)
Public Delegate Sub CalculatePrimeCompletedEventHandler( _
ByVal sender As Object, _
ByVal e As CalculatePrimeCompletedEventArgs)
' This class implements the Event-based Asynchronous Pattern.
' It asynchronously computes whether a number is prime or
' composite (not prime).
Public Class PrimeNumberCalculator
Inherits System.ComponentModel.Component
Private Delegate Sub WorkerEventHandler( _
ByVal numberToCheck As Integer, _
ByVal asyncOp As AsyncOperation)
Private onProgressReportDelegate As SendOrPostCallback
Private onCompletedDelegate As SendOrPostCallback
Private userStateToLifetime As New HybridDictionary()
Private components As System.ComponentModel.Container = Nothing
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "Public events"
Public Event ProgressChanged _
As ProgressChangedEventHandler
Public Event CalculatePrimeCompleted _
As CalculatePrimeCompletedEventHandler
#End Region
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "Construction and destruction"
Public Sub New(ByVal container As System.ComponentModel.IContainer)
container.Add(Me)
InitializeComponent()
InitializeDelegates()
End Sub
Public Sub New()
InitializeComponent()
InitializeDelegates()
End Sub
Protected Overridable Sub InitializeDelegates()
onProgressReportDelegate = _
New SendOrPostCallback(AddressOf ReportProgress)
onCompletedDelegate = _
New SendOrPostCallback(AddressOf CalculateCompleted)
End Sub
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If (components IsNot Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#End Region
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "Implementation"
' 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
' 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
' 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 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 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 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 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 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
#End Region
Private Sub InitializeComponent()
End Sub
End Class
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 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
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;
...
/////////////////////////////////////////////////////////////
#region PrimeNumberCalculator Implementation
public delegate void ProgressChangedEventHandler(
ProgressChangedEventArgs e);
public delegate void CalculatePrimeCompletedEventHandler(
object sender,
CalculatePrimeCompletedEventArgs e);
// This class implements the Event-based Asynchronous Pattern.
// It asynchronously computes whether a number is prime or
// composite (not prime).
public class PrimeNumberCalculator : Component
{
private delegate void WorkerEventHandler(
int numberToCheck,
AsyncOperation asyncOp);
private SendOrPostCallback onProgressReportDelegate;
private SendOrPostCallback onCompletedDelegate;
private HybridDictionary userStateToLifetime =
new HybridDictionary();
private System.ComponentModel.Container components = null;
/////////////////////////////////////////////////////////////
#region Public events
public event ProgressChangedEventHandler ProgressChanged;
public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
#endregion
/////////////////////////////////////////////////////////////
#region Construction and destruction
public PrimeNumberCalculator(IContainer container)
{
container.Add(this);
InitializeComponent();
InitializeDelegates();
}
public PrimeNumberCalculator()
{
InitializeComponent();
InitializeDelegates();
}
protected virtual void InitializeDelegates()
{
onProgressReportDelegate =
new SendOrPostCallback(ReportProgress);
onCompletedDelegate =
new SendOrPostCallback(CalculateCompleted);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#endregion // Construction and destruction
/////////////////////////////////////////////////////////////
///
#region Implementation
// 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);
}
// Utility method for determining if a
// task has been canceled.
private bool TaskCanceled(object taskId)
{
return( userStateToLifetime[taskId] == null );
}
// 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 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);
}
// 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 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 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);
}
}
// 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.
}
#endregion
/////////////////////////////////////////////////////////////
#region Component Designer generated code
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
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 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;
}
}
}
#endregion
See Also
Tasks
Walkthrough: Implementing a Component That Supports the Event-based Asynchronous Pattern