控件 | 名称 | 文本 |
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 方法以异步调用这些方法。可以从窗体 (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。 找到下列代码的每个实例:
应该有此代码的四个实例,每个计算方法中有一个。 修改此代码,使其显示为如下形式:
保存工作,并按上例所示进行测试。 您可能注意到对程序性能的细微影响。这是因为当组件获得排他锁后,线程的执行停止。尽管它保证了正确性,但这种方法抵消了多线程带来的某些性能优点。应该认真考虑锁定线程的必要性,并且仅当绝对必要时才予以实现。 |