逐步解說:實作支援事件架構非同步模式的元件
如果您在撰寫的類別具有一些可能造成顯著延遲的作業,請考慮實作事件架構非同步模式概觀以給予該類別非同步的功能。
本逐步解說會說明如何建立實作事件架構非同步模式的元件。 此元件的實作,是使用 System.ComponentModel 命名空間中的 Helper 類別來進行的。如此可確定元件會在任何應用程式模型 (包括 ASP.NET、主控台應用程式和 Windows Form 應用程式) 底下,正確地進行運作。 運用 PropertyGrid 控制項和您自己的自訂控制項,也都能夠設計這個元件。
在完成時,您就會擁有能以非同步方式計算質數值的一個應用程式。 您的應用程式將會具有一個主使用者介面 (UI) 執行緒,以及用於每次質數計算的另一個執行緒。 儘管測試大型數字是否為質數需要一些時間,主 UI 執行緒依然不會被這項延遲中斷,而且在進行計算時,表單也會有回應的能力。 您將能夠盡量執行並行計算,並能選擇性地取消擱置的計算。
逐步解說將說明的工作包括:
建立元件
定義公用非同步事件和委派 (Delegate)
定義私用委派
實作公用事件
實作 Completion 方法
實作 Worker 方法
實作 Start 和 Cancel 方法
若要將此主題中的程式碼複製為一份清單,請參閱 HOW TO:實作支援事件架構非同步模式的元件。
建立元件
第一個步驟就是建立將會實作事件架構非同步模式的元件。
若要建立元件
- 建立繼承自 Component、稱為 PrimeNumberCalculator 的類別。
定義公用非同步事件和委派
元件會使用事件來與用戶端通訊。 MethodNameCompleted 事件會向用戶端警示非同步工作的完成,而 MethodNameProgressChanged 事件則會向用戶端通知非同步工作的進度。
若要為元件的用戶端定義非同步事件:
在檔案的頂端,匯入 System.Threading 和 System.Collections.Specialized 命名空間。
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;
在 PrimeNumberCalculator 類別定義之前,宣告進度和完成事件的委派。
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);
在 PrimeNumberCalculator 類別定義中,宣告事件以對用戶端報告進度和完成。
Public Event ProgressChanged _ As ProgressChangedEventHandler Public Event CalculatePrimeCompleted _ As CalculatePrimeCompletedEventHandler
public event ProgressChangedEventHandler ProgressChanged; public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
在 PrimeNumberCalculator 類別定義之後,衍生 CalculatePrimeCompletedEventArgs 類別,以便將每次計算的結果,都報告到 CalculatePrimeCompleted 事件的用戶端事件處理常式。 除了 AsyncCompletedEventArgs 屬性,這個類別還讓用戶端能夠決定要測試何種數值、該數值是否為質數,以及該數值不為質數時所具有的第一個除數。
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; } } }
檢查點
完成這個步驟之後,您就可以建置元件了。
若要測試您的元件
編譯元件。
您將會收到兩項編譯器警告:
warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used
這些警告將會在下一區段中清除。
定義私用委派
PrimeNumberCalculator 元件的非同步概念,是由稱為 SendOrPostCallback 的特殊委派,在內部進行實作的。 SendOrPostCallback 表示在 ThreadPool 執行緒上所執行的回呼 (Callback) 方法。 此回呼方法必須具有使用 Object 型別之單一參數的簽章 (Signature),這表示您將需要在包裝函式類別 (Wrapper Class) 中的委派之中傳遞狀態。 如需詳細資訊,請參閱 SendOrPostCallback。
若要實作元件的內部非同步行為:
在 PrimeNumberCalculator 類別中,宣告和建立 SendOrPostCallback 委派。 在稱為 InitializeDelegates 的公用程式方法中,建立 SendOrPostCallback 物件。
您將需要兩個委派:一個用於向用戶端回報進度,另一個則用於向用戶端回報已完成作業。
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); }
在您的元件建構函式 (Constructor) 內呼叫 InitializeDelegates 方法。
Public Sub New() InitializeComponent() InitializeDelegates() End Sub
public PrimeNumberCalculator() { InitializeComponent(); InitializeDelegates(); }
在處理需要非同步地完成之實際工作的 PrimeNumberCalculator 類別中,宣告一個委派。 這個委派會包裝測試數值是否為質數的 Worker 方法。 這個委派會使用 AsyncOperation 參數來追蹤非同步作業的存留期 (Lifetime)。
Private Delegate Sub WorkerEventHandler( _ ByVal numberToCheck As Integer, _ ByVal asyncOp As AsyncOperation)
private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp);
為擱置中非同步運算建立管理存留期的集合。 在作業執行和完成時,用戶端需要具有追蹤這些作業的方法。若要進行這種追蹤,需要用戶端在對非同步方法進行呼叫時,傳遞唯一語彙基元 (Token,又稱為工作 ID)。 PrimeNumberCalculator 元件必須使工作 ID 與其對應的引動過程關聯,以持續追蹤每次呼叫。 如果用戶端傳遞的工作 ID 並非唯一,PrimeNumberCalculator 元件就必須引發例外狀況。
PrimeNumberCalculator 元件會使用稱為 HybridDictionary 的特殊集合類別,持續追蹤工作 ID。 在類別定義中,建立一個稱為 userTokenToLifetime 的 HybridDictionary。
Private userStateToLifetime As New HybridDictionary()
private HybridDictionary userStateToLifetime = new HybridDictionary();
實作公用事件
實作事件架構非同步模式的元件,都會使用事件來與用戶端通訊。 藉由 AsyncOperation 類別的協助,這些事件會在適當的執行緒上被叫用 (Invoke)。
若要對元件的用戶端引發事件:
實作公用事件以向用戶端報告。 您將需要一個用來報告進度的事件,以及一個用來報告完成的事件。
' 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); } }
實作 Completion 方法
完成委派就是在非同步作業由於成功完成、錯誤或取消而結束時,便會由基礎、無限制執行緒的非同步行為所叫用的方法。 這個引動過程會在任意執行緒上發生。
這個方法,也就是從唯一用戶端語彙基元的內部集合中,移除用戶端工作 ID 的地方。 這個方法也會呼叫對應 AsyncOperation 上的 PostOperationCompleted 方法,以結束特定非同步作業的存留期。 這個呼叫會在適合於應用程式模型的執行緒上,引發完成事件。 在呼叫 PostOperationCompleted 方法之後,就會無法再使用這個 AsyncOperation 執行個體,而且使用該執行個體的任何嘗試,都會擲回例外狀況。
CompletionMethod 簽章必須持有說明非同步作業結果的所有狀態。 此類別持有這個特定非同步作業所測試之數值的狀態:數值是否為質數,以及不為質數時所具有的第一個除數。 此類別也持有用來描述發生之任何例外狀況及對應於此特定工作之 AsyncOperation 的狀態。
若要完成非同步作業:
實作完成方法。 這個方法會使用六個參數,以填入將會透過用戶端的 CalculatePrimeCompletedEventHandler 傳回給用戶端的 CalculatePrimeCompletedEventArgs。 此方法還會從內部集合中移除用戶端的工作 ID 語彙基元,並以對於 PostOperationCompleted 的呼叫來結束非同步作業的存留期。 AsyncOperation 則會將此呼叫封送處理 (Marshal) 至執行緒,或是適合於應用程式模型的內容。
' 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. }
檢查點
完成這個步驟之後,您就可以建置元件了。
若要測試您的元件
編譯元件。
您將會收到一項編譯器警告:
warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used
這些警告將會在下一區段中解決。
實作 Worker 方法
直到目前為止,您已經實作了 PrimeNumberCalculator 元件的支援非同步程式碼。 現在您就可以實作進行實際工作的程式碼。 您將會實作三種方法 CalculateWorker、 BuildPrimeNumberList 和 IsPrime。 在一起使用時,BuildPrimeNumberList 和 IsPrime 組成了稱為 Sieve of Eratosthenes 的著名演算式,此演算式會找出直到測試數值平方根的所有質數,以判斷該數值是否為質數。 如果到時候都沒有找到任何除數,則表示測試數值就是質數。
如果這個元件的撰寫是為了提供最高效率,就會記得針對不同測試數值的各種引動過程所探索到的所有質數。 此元件也會檢查 2、3、5 之類的普通除數。 然而,這個範例的目的是要示範非同步作業的執行會如何耗費時間,因此這些最佳化的事項都將留給您做為練習。
CalculateWorker 方法包裝在委派中,而且會以 BeginInvoke 的呼叫來非同步叫用。
注意事項 |
---|
進度回報會在 BuildPrimeNumberList 方法中實作。在速度快的電腦中,可以在快速執行時引發 ProgressChanged 事件。用戶端執行緒 (即引發這些事件的執行緒) 必須能夠處理這個情況。使用者介面的程式碼可能充滿了訊息且無法保留,導致懸置行為的產生。如需處理這個情況的使用者介面範例,請參閱 HOW TO:實作事件架構非同步模式的用戶端。 |
若要非同步地執行質數計算:
實作 TaskCanceled 公用程式方法。 這個方法會檢查工作存留期集合,以找出指定的工作 ID,而如果找不到此工作 ID,就會傳回 true。
' 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 ); }
實作 CalculateWorker 方法。 這項實作需要兩個參數:要測試的數值,以及 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); }
實作 BuildPrimeNumberList。 這項實作需要使用兩個參數:要測試的數值,以及 AsyncOperation。 此實作會使用 AsyncOperation 來報告進度和累加結果。 如此就能確定用戶端的事件處理常式,會在適當的執行緒或應用程式模型的內容中被呼叫。 在 BuildPrimeNumberList 找到質數時,就會將此報告為 ProgressChanged 事件之用戶端事件處理常式的累加結果。 此時需要使用衍生自 ProgressChangedEventArgs 的類別 (稱為 CalculatePrimeProgressChangedEventArgs),此類別包含一個添加的屬性,稱為 LatestPrimeNumber。
BuildPrimeNumberList 方法也會定期呼叫 TaskCanceled 方法,並在此方法傳回 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; }
實作 IsPrime。 這項實作需要使用三個參數:一份已知質數的清單、要測試的數值,以及找到的第一個除數的輸出參數。 利用質數的清單,此實作即可判斷測試數值是否為質數。
' 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; }
從 ProgressChangedEventArgs 衍生 CalculatePrimeProgressChangedEventArgs。 要向 ProgressChanged 事件的用戶端事件處理常式報告累加結果,這個類別是必要的。 此類別有一個稱為 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; } } }
檢查點
完成這個步驟之後,您就可以建置元件了。
若要測試您的元件
編譯元件。
還需要撰寫的部分,只剩下啟動和取消非同步作業的方法,也就是 CalculatePrimeAsync 和 CancelAsync。
實作 Start 和 Cancel 方法
在包裝 Worker 方法的委派上呼叫 BeginInvoke,即可讓 Worker 方法在自己的執行緒上啟動。 若要管理特定非同步作業的存留期,您可以呼叫 AsyncOperationManager Helper 類別上的 CreateOperation 方法。 這樣就會傳回 AsyncOperation,它會將用戶端事件處理常式的呼叫封送處理至適當的執行緒或內容。
藉由在其對應的 AsyncOperation 上呼叫 PostOperationCompleted,即可取消特定的擱置中的作業。 這樣就會結束作業,而且對於 AsyncOperation 的任何後續呼叫,也都會擲回例外狀況。
若要實作啟動和取消功能:
實作 CalculatePrimeAsync 方法。 確定用戶端支援的語彙基元 (工作 ID),在表示目前擱置工作的所有語彙基元中是唯一的。 如果用戶端傳入的語彙基元並非唯一,CalculatePrimeAsync 就會引發例外狀況。 否則,語彙基元就會加入到工作 ID 集合中。
' 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); }
實作 CancelAsync 方法。 如果 taskId 參數存在於語彙基元 (Token) 集合內,則會被移除。 如此一來,可避免執行尚未啟動的取消工作。 如果該工作正在執行中,則當 BuildPrimeNumberList 方法偵測到此工作 ID 已從存留期集合中移除時,就會結束。
' 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); } } }
檢查點
完成這個步驟之後,您就可以建置元件了。
若要測試您的元件
- 編譯元件。
PrimeNumberCalculator 元件現在已經完成,並且已經可以使用。
如需使用 PrimeNumberCalculator 元件的範例用戶端,請參閱 HOW TO:實作事件架構非同步模式的用戶端。
後續步驟
您可以藉由撰寫 CalculatePrime,也就是 CalculatePrimeAsync 方法的同步對等方法,來填寫這個範例。 這樣就會讓 PrimeNumberCalculator 元件完全遵循事件架構非同步模式。
保留針對不同測試數值的各種引動過程所探索到所有質數清單,即可改進這個範例。 只要使用這種處理方法,每項工作都會受益於先前完成的工作。 請以 lock 區域小心保護這份清單,如此即可序列化不同執行緒對此清單的存取。
您也可以測試普通除數 (像是 2、3 和 5),以改進這個範例。