控件 | 名稱 | 文本 |
Label1 | lblFactorial1 | (空白) |
Label2 | lblFactorial2 | (空白) |
Label3 | lblAddTwo | (空白) |
Label4 | lblRunLoops | (空白) |
Label5 | lblTotalCalculations | (空白) |
Button1 | btnFactorial1 | Factorial |
Button2 | btnFactorial2 | Factorial - 1 |
Button3 | btnAddTwo | Add Two |
Button4 | btnRunLoops | Run a Loop |
Textbox1 | txtValue | (空白) |
public int varAddTwo; public int varFact1; public int varFact2; public int varLoopValue; public double varTotalCalculations = 0; |
// This delegate will be invoked with two of your events. public delegate void FactorialCompleteHandler(double Factorial, double TotalCalculations); public delegate void AddTwoCompleteHandler(int Result, double TotalCalculations); public delegate void LoopCompleteHandler(double TotalCalculations, int Counter); |
public event FactorialCompleteHandler FactorialComplete; public event FactorialCompleteHandler FactorialMinusOneComplete; public event AddTwoCompleteHandler AddTwoComplete; public event LoopCompleteHandler LoopComplete; |
// This method will calculate the value of a number minus 1 factorial // (varFact2-1!). public void FactorialMinusOne() { double varTotalAsOfNow = 0; double varResult = 1; // Performs a factorial calculation on varFact2 - 1. for (int varX = 1; varX <= varFact2 - 1; varX++) { varResult *= varX; // Increments varTotalCalculations and keeps track of the current // total as of this instant. varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } // Signals that the method has completed, and communicates the // result and a value of total calculations performed up to this // point. FactorialMinusOneComplete(varResult, varTotalAsOfNow); } // This method will calculate the value of a number factorial. // (varFact1!) public void Factorial() { double varResult = 1; double varTotalAsOfNow = 0; for (int varX = 1; varX <= varFact1; varX++) { varResult *= varX; varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } FactorialComplete(varResult, varTotalAsOfNow); } // This method will add two to a number (varAddTwo+2). public void AddTwo() { double varTotalAsOfNow = 0; int varResult = varAddTwo + 2; varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; AddTwoComplete(varResult, varTotalAsOfNow); } // This method will run a loop with a nested loop varLoopValue times. public void RunALoop() { int varX; double varTotalAsOfNow = 0; for (varX = 1; varX <= varLoopValue; varX++) { // This nested loop is added solely for the purpose of slowing down // the program and creating a processor-intensive application. for (int varY = 1; varY <= 500; varY++) { varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } } LoopComplete(varTotalAsOfNow, varLoopValue); } |
Calculator Calculator1; |
// Creates a new instance of Calculator. Calculator1 = new Calculator(); |
private void btnFactorial1_Click(object sender, System.EventArgs e) // Passes the value typed in the txtValue to Calculator.varFact1. { Calculator1.varFact1 = int.Parse(txtValue.Text); // Disables the btnFactorial1 until this calculation is complete. btnFactorial1.Enabled = false; Calculator1.Factorial(); } private void btnFactorial2_Click(object sender, System.EventArgs e) { Calculator1.varFact2 = int.Parse(txtValue.Text); btnFactorial2.Enabled = false; Calculator1.FactorialMinusOne(); } private void btnAddTwo_Click(object sender, System.EventArgs e) { Calculator1.varAddTwo = int.Parse(txtValue.Text); btnAddTwo.Enabled = false; Calculator1.AddTwo(); } private void btnRunLoops_Click(object sender, System.EventArgs e) { Calculator1.varLoopValue = int.Parse(txtValue.Text); btnRunLoops.Enabled = false; // Lets the user know that a loop is running lblRunLoops.Text = "Looping"; Calculator1.RunALoop(); } |
protected void FactorialHandler(double Value, double Calculations) // Displays the returned value in the appropriate label. { lblFactorial1.Text = Value.ToString(); // Re-enables the button so it can be used again. btnFactorial1.Enabled = true; // Updates the label that displays the total calculations performed lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } protected void FactorialMinusHandler(double Value, double Calculations) { lblFactorial2.Text = Value.ToString(); btnFactorial2.Enabled = true; lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } protected void AddTwoHandler(int Value, double Calculations) { lblAddTwo.Text = Value.ToString(); btnAddTwo.Enabled = true; lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } protected void LoopDoneHandler(double Calculations, int Count) { btnRunLoops.Enabled = true; lblRunLoops.Text = Count.ToString(); lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } |
Calculator1.FactorialComplete += new Calculator.FactorialCompleteHandler(this.FactorialHandler); Calculator1.FactorialMinusOneComplete += new Calculator.FactorialCompleteHandler(this.FactorialMinusHandler); Calculator1.AddTwoComplete += new Calculator.AddTwoCompleteHandler(this.AddTwoHandler); Calculator1.LoopComplete += new Calculator.LoopCompleteHandler(this.LoopDoneHandler); |
// Declares the variables you will use to hold your thread objects. public System.Threading.Thread FactorialThread; public System.Threading.Thread FactorialMinusOneThread; public System.Threading.Thread AddTwoThread; public System.Threading.Thread LoopThread; |
public void ChooseThreads(int threadNumber) { // Determines which thread to start based on the value it receives. switch(threadNumber) { case 1: // Sets the thread using the AddressOf the subroutine where // the thread will start. FactorialThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.Factorial)); // Starts the thread. FactorialThread.Start(); break; case 2: FactorialMinusOneThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.FactorialMinusOne)); FactorialMinusOneThread.Start(); break; case 3: AddTwoThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.AddTwo)); AddTwoThread.Start(); break; case 4: LoopThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.RunALoop)); LoopThread.Start(); break; } } |
// Calculator1.Factorial() |
// Passes the value 1 to Calculator1, thus directing it to start the // correct thread. Calculator1.ChooseThreads(1); |
protected void btnFactorial1_Click(object sender, System.EventArgs e) // Passes the value typed in the txtValue to Calculator.varFact1 { Calculator1.varFact1 = int.Parse(txtValue.Text); // Disables the btnFactorial1 until this calculation is complete btnFactorial1.Enabled = false; // Calculator1.Factorial(); Calculator1.ChooseThreads(1); } protected void btnFactorial2_Click(object sender, System.EventArgs e) { Calculator1.varFact2 = int.Parse(txtValue.Text); btnFactorial2.Enabled = false; // Calculator1.FactorialMinusOne(); Calculator1.ChooseThreads(2); } protected void btnAddTwo_Click(object sender, System.EventArgs e) { Calculator1.varAddTwo = int.Parse(txtValue.Text); btnAddTwo.Enabled = false; // Calculator1.AddTwo(); Calculator1.ChooseThreads(3); } protected void btnRunLoops_Click(object sender, System.EventArgs e) { Calculator1.varLoopValue = int.Parse(txtValue.Text); btnRunLoops.Enabled = false; // Lets the user know that a loop is running lblRunLoops.Text = "Looping"; // Calculator1.RunALoop(); Calculator1.ChooseThreads(4); } |
封送處理對控件的調用 現在將加速窗體上的顯示更新。鑒于控件總是由主執行線程所有,從屬線程中對控件的任何調用都需要“封送處理”調用。封送處理是跨線程邊界移動調用的行為,需要耗費大量的資源。為了使需要發生的封送處理量減到最少,并確保以線程安全方式處理調用,應使用 Control.BeginInvoke 方法來調用主執行線程上的方法,從而使必須發生的跨線程邊界的封送處理量減到最少。當調用操作控件的方法時,這種調用非常必要。有關詳細信息,請參見從線程操作控件。 創建控件調用過程 為 frmCalculations 打開代碼編輯器。在聲明部分,添加下列代碼:
Invoke 和 BeginInvoke 需要將適當方法的委托作為參數。這些代碼行聲明一些委托簽名,這些簽名將被 BeginInvoke 用于調用適當的方法。 在代碼中添加下列空方法。
在“編輯”菜單中,使用“剪切”和“粘貼”,從 FactorialHandler 方法中剪切所有代碼,并將其粘貼到 FactHandler 中。 對 FactorialMinusHandler 和 Fact1Handler、AddTwoHandler 和 Add2Handler 以及 LoopDoneHandler 和 LDoneHandler 重復上面的步驟。 完成后,在 FactorialHandler、Factorial1Handler、AddTwoHandler 和 LoopDoneHandler 中應該沒有剩余代碼,并且它們曾經包含的所有代碼應該已經移動到適當的新方法中。 調用 BeginInvoke 方法以異步調用這些方法??梢詮拇绑w (this) 或者窗體上的任何控件調用 BeginInvoke。 完成后,代碼看起來應該類似以下形式:
看起來似乎事件處理程序僅僅是對下一個方法進行調用。實際上,該事件處理程序實現了在主操作線程上調用方法。這種方法可節省跨線程邊界的調用,并使多線程應用程序能夠有效運行而不必擔心導致死鎖。有關在多線程環境下使用控件的詳細信息,請參見從線程操作控件。 保存您的工作。 從“調試”菜單中選擇“啟動”,測試該解決方案。 在文本框內鍵入 10000000 并單擊“運行循環”。 此按鈕下方的標簽中顯示“Looping”。運行這個循環應該占用很長時間。如果它完成得太快,請相應地調整該數字的大小。 連續地快速單擊仍在啟用的三個按鈕。您會發現所有按鈕都響應您的輸入。在“Add Two”下方的標簽應該第一個顯示結果。結果稍后將顯示在階乘按鈕下方的標簽中。估計這些結果會無限大,因為 10,000,000 的階乘返回的數字對于雙精度變量而言太大,以至超出了它包含的范圍。最后,再過片刻,結果將返回到“運行循環”按鈕下方。 正如剛剛觀察到的,在四個單獨的線程上同時執行四組獨立的計算。用戶界面保持對輸入的響應,并在每個線程完成后返回結果。 協調線程 有經驗的多線程應用程序用戶可能會發現已鍵入的代碼中存在細微缺陷。從 Calculator.cs 中每個執行計算的子例程中撤回以下代碼行:
這兩行代碼遞增公共變量 varTotalCalculations 并將局部變量 varTotalAsOfNow 設為此值。然后,該值被返回給 frmCalculations,并顯示在標簽控件中。但返回的值正確嗎?如果只有單個執行線程在運行,則答案明顯是正確的。但是如果有多個線程在運行,答案則變得不太確定。每個線程都具有遞增變量 varTotalCalculations 的能力。有可能出現這樣的情況:在一個線程遞增該變量之后,但在它將該值復制到 varTotalAsOfNow 之前,另一個線程可能通過遞增該變量而更改它的值。這將導致有可能每個線程實際上在報告不正確的結果。Visual C# 提供 lock 語句語句以允許線程的同步,從而確保每個線程始終返回準確的結果。lock 的語法如下所示:
輸入 lock 塊后,在指定的線程對所討論的對象擁有專用鎖之前,對指定表達式的執行一直被堵塞。在上面顯示的示例中,對 AnObject 的執行處于鎖定狀態。必須對返回引用的對象(而非返回值的對象)使用 lock。然后,執行以塊的形式繼續進行,而不會受到其他線程的干擾。作為一個單元執行的語句集稱為“原子”。當遇到 } 時,表達式將被釋放,線程可繼續正常工作。 將 lock 語句添加到應用程序 在代碼編輯器中打開 Calculator.cs。 找到下列代碼的每個實例:
應該有此代碼的四個實例,每個計算方法中有一個。 修改此代碼,使其顯示為如下形式:
保存工作,并按上例所示進行測試。 您可能注意到對程序性能的細微影響。這是因為當組件獲得排他鎖后,線程的執行停止。盡管它保證了正確性,但這種方法抵消了多線程帶來的某些性能優點。應該認真考慮鎖定線程的必要性,并且僅當絕對必要時才予以實現。 |