
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
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.
' 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 );
}
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);
}
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.
' 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;
}
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;
}
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.
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;
}
}
}