Documentos de Académico
Documentos de Profesional
Documentos de Cultura
http://msdn.microsoft.com/en-us/library/48cfdff6
Note
Although a function is usually preferable for a method that calculates a value, arguments cannot be passed between threads, nor can values be returned. There are many simple ways to supply values to threads and to receive values from them. In this demonstration, you will return values to your user interface by updating public variables, and events will be used to notify the main program when a thread has completed execution. The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Working with Settings.
1 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
Control
label1 label2 label3 label4 label5 button1 button2 button3 button4 textBox1
Name
lblFactorial1 lblFactorial2 lblAddTwo lblRunLoops lblTotalCalculations btnFactorial1 btnFactorial2 btnAddTwo btnRunLoops txtValue
Text
(blank) (blank) (blank) (blank) (blank) Factorial Factorial - 1 Add Two Run a Loop (blank)
int varAddTwo; int varFact1; int varFact2; int varLoopValue; double varTotalCalculations = 0;
2 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
Note
Although you will be declaring four events, you only need to create three delegates, because two events will have the same signature. Immediately beneath the variable declarations entered in the previous step, type the following code:
// 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); 2. Declare the events that your component will use to communicate with your application. Do this by adding the following code immediately beneath the code entered in the previous step.
3. Immediately beneath the code you typed in the previous step, type the following code:
// 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;
3 of 13
5/25/2012 11:39 AM
Walkthrough: Authoring a Simple Multithreaded Component with Visual C# for (int varX = 1; varX <= varFact1; varX++) { varResult *= varX; varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } FactorialComplete(varResult, varTotalAsOfNow);
http://msdn.microsoft.com/en-us/library/48cfdff6
// 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; 3. Locate the constructor. Immediately before the }, add the following line:
4 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
// Creates a new instance of Calculator. Calculator1 = new Calculator(); 4. In the designer, click each button to generate the code outline for each control's Click event handlers and add code to create the handlers. When complete, your Click event handlers should resemble the following:
// Passes the value typed in the txtValue to Calculator.varFact1. private void btnFactorial1_Click(object sender, System.EventArgs e) { 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(); } 5. After the code you added in the previous step, type the following to handle the events your form will receive from Calculator1:
private 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(); } private void FactorialMinusHandler(double Value, double Calculations)
5 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
private void AddTwoHandler(int Value, double Calculations) { lblAddTwo.Text = Value.ToString(); btnAddTwo.Enabled = true; lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } private void LoopDoneHandler(double Calculations, int Count) { btnRunLoops.Enabled = true; lblRunLoops.Text = Count.ToString(); lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } 6. In the constructor of frmCalculations, add the following code immediately before the } to handle the custom events your form will receive from Calculator1.
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);
6 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
4. Change the value in the text box to 20, then click the button labeled Factorial. The number "2.43290200817664E+18" is displayed beneath it, and lblTotalCalculations now reads "Total Calculations are 24." 5. Change the value in the text box to 50000, and then click the button labeled Run A Loop. Note that there is a small but noticeable interval before this button is re-enabled. The label under this button should read "50000" and the total calculations displayed are "25000024." 6. Change the value in the text box to 5000000 and click the button labeled Run A Loop, then immediately click the button labeled Add Two. Click it again. The button does not respond, nor will any control on the form respond until the loops have been completed. If your program runs only a single thread of execution, processor-intensive calculations such as the above example have the tendency to tie up the program until the calculations have been completed. In the next section, you will add multithreading capability to your application so that multiple threads may be run at once.
// 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; 3. Immediately before the end of the class declaration at the bottom of the code, add the following method:
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();
7 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
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;
When a Thread is instantiated, it requires an argument in the form of a ThreadStart. The ThreadStart is a delegate that points to the address of the method where the thread is to begin. A ThreadStart cannot take parameters or pass values, and therefore can only indicate a void method. The ChooseThreads method you just implemented will receive a value from the program calling it and use that value to determine the appropriate thread to start.
// Passes the value 1 to Calculator1, thus directing it to start the // correct thread. Calculator1.ChooseThreads(1); 2. Make similar modifications to the other button_click methods.
Note
Be certain to include the appropriate value for the Threads argument. When you have finished, your code should look similar to the following:
8 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
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(); Calculator1.ChooseThreads(1); } private void btnFactorial2_Click(object sender, System.EventArgs e) { Calculator1.varFact2 = int.Parse(txtValue.Text); btnFactorial2.Enabled = false; // Calculator1.FactorialMinusOne(); Calculator1.ChooseThreads(2); } private void btnAddTwo_Click(object sender, System.EventArgs e) { Calculator1.varAddTwo = int.Parse(txtValue.Text); btnAddTwo.Enabled = false; // Calculator1.AddTwo(); Calculator1.ChooseThreads(3); } 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(); Calculator1.ChooseThreads(4); }
public delegate void FHandler(double Value, double Calculations); public delegate void A2Handler(int Value, double Calculations); public delegate void LDHandler(double Calculations, int Count);
9 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
Invoke and BeginInvoke require a delegate to the appropriate method as an argument. These lines declare the delegate signatures that will be used by BeginInvoke to invoke the appropriate methods. 2. Add the following empty methods to your code.
void FactHandler(double Value, double Calculations) void Fact1Handler(double Value, double Calculations) void Add2Handler(int Value, double Calculations) void LDoneHandler(double Calculations, int Count)
3. From the Edit menu, use Cut and Paste to cut all the code from the method FactorialHandler and paste it into FactHandler. 4. Repeat the previous step for FactorialMinusHandler and Fact1Handler, AddTwoHandler and Add2Handler, and LoopDoneHandler and LDoneHandler. When finished, there should be no code remaining in FactorialHandler, Factorial1Handler, AddTwoHandler, and LoopDoneHandler, and all the code these used to contain should have been moved to the appropriate new methods. 5. Call the BeginInvoke method to invoke the methods asynchronously. You can call BeginInvoke from either your form (this) or any of the controls on the form. When complete, your code should look similar to the following:
protected void FactorialHandler(double Value, double Calculations) { // BeginInvoke causes asynchronous execution to begin at the address // specified by the delegate. Simply put, it transfers execution of // this method back to the main thread. Any parameters required by // the method contained at the delegate are wrapped in an object and // passed. this.BeginInvoke(new FHandler(FactHandler), new Object[] {Value, Calculations}); } protected void FactorialMinusHandler(double Value, double Calculations) { this.BeginInvoke(new FHandler(Fact1Handler), new Object [] {Value, Calculations}); } protected void AddTwoHandler(int Value, double Calculations) { this.BeginInvoke(new A2Handler(Add2Handler), new Object[] {Value, Calculations});
10 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
protected void LoopDoneHandler(double Calculations, int Count) { this.BeginInvoke(new LDHandler(LDoneHandler), new Object[] {Calculations, Count}); } It may seem as though the event handler is simply making a call to the next method. The event handler is actually causing a method to be invoked on the main thread of operation. This approach saves on calls across thread boundaries and allows your multithreaded applications to run efficiently and without fear of causing lockup. For details on working with controls in a multithreaded environment, see How to: Manipulate Controls from Threads. 6. Save your work. 7. Test your solution by choosing Start Debugging from the Debug menu. a. Type 10000000 in the text box and click Run A Loop. "Looping" is displayed in the label beneath this button. This loop should take a significant amount of time to run. If it completes too early, adjust the size of the number accordingly. b. In rapid succession, click all three buttons that are still enabled. You will find that all buttons respond to your input. The label beneath Add Two should be the first to display a result. Results will later be displayed in the labels beneath the factorial buttons. These results evaluate to infinity, as the number returned by a 10,000,000 factorial is too large for a double-precision variable to contain. Finally, after an additional delay, results are returned beneath the Run A Loop button. As you just observed, four separate sets of calculations were performed simultaneously upon four separate threads. The user interface remained responsive to input, and results were returned after each thread completed.
varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; These two lines of code increment the public variable varTotalCalculations and set the local variable varTotalAsOfNow to this value. This value is then returned to frmCalculations and displayed in a label control. But is the correct value being returned? If only a single thread of execution is running, the answer is clearly yes. But if multiple threads are running, the answer becomes more uncertain. Each thread has the ability to increment the variable varTotalCalculations. It is possible that after one thread increments this variable, but before it copies the value to varTotalAsOfNow, another thread could alter the value of this variable by incrementing it. This leads to the possibility that each thread is, in fact, reporting inaccurate results. Visual C# provides the lock Statement (C# Reference) to allow synchronization of threads to ensure that each thread always returns an accurate result. The syntax for lock is as follows:
11 of 13
5/25/2012 11:39 AM
http://msdn.microsoft.com/en-us/library/48cfdff6
lock(AnObject) { // Insert code that affects the object. // Insert more code that affects the object. // Insert more code that affects the object. // Release the lock. } When the lock block is entered, execution on the specified expression is blocked until the specified thread has an exclusive lock on the object in question. In the example shown above, execution is blocked on AnObject. lock must be used with an object that returns a reference rather than a value. The execution may then proceed as a block without interference from other threads. A set of statements that execute as a unit are said to be atomic. When the } is encountered, the expression is freed and the threads are allowed to proceed normally.
varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; There should be four instances of this code, one in each calculation method. 3. Modify this code so that it reads as follows:
lock(this) { varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } 4. Save your work and test it as in the previous example. You may notice a slight impact on the performance of your program. This is because execution of threads stops when an exclusive lock is obtained on your component. Although it ensures accuracy, this approach impedes some of the performance benefits of multiple threads. You should carefully consider the need for locking threads, and implement them only when absolutely necessary.
See Also
Tasks How to: Coordinate Multiple Threads of Execution Walkthrough: Authoring a Simple Multithreaded Component with Visual Basic Reference BackgroundWorker Concepts
12 of 13
5/25/2012 11:39 AM
Walkthrough: Authoring a Simple Multithreaded Component with Visual C# Event-based Asynchronous Pattern Overview Other Resources Programming with Components Component Programming Walkthroughs Multithreading in Components
http://msdn.microsoft.com/en-us/library/48cfdff6
Yes
No
Community Content
2012 Microsoft. All rights reserved.
13 of 13
5/25/2012 11:39 AM