Visual| multithreading allows you to write applications that can perform multiple tasks at the same time. This capability, called multithreading or free threading, is a powerful way to design components that are processor-intensive and require user input. The component that calculates payroll information is an example of a component that can take advantage of multithreaded processing. The component can process data entered into a database on one thread while performing a payroll calculation that frequently uses a processor on another thread. By running these processes on different threads, the user can enter additional data without waiting for the computer to complete the calculation. In this walkthrough, you will create a simple multithreaded component that can perform several complex computations at the same time.
Create a project
The application will include a single form and a component. The user enters a value and instructs the component to start the calculation. The form then receives the value from the component and displays it in the label control. The component performs computations that frequently use the processor and notifies the form when it is finished. You will create a public variable in the component to hold the values received from the user interface. You will also implement methods in your component that perform calculations based on the values of those variables.
Note Although functions are generally preferable for methods of calculating values, they cannot pass arguments between threads and cannot return a value. There are a number of simple ways to provide values to threads and receive values from threads. In this demo, the value is returned to the user interface by updating the public variable, and the event is used to notify the main program when the thread has finished executing.
Create a form
Create a new Windows application project.
Name the application calculations and rename the Form1.cs to FrmCalculations.cs.
The form will be used as the primary user interface for the application.
Double-click the form on the designer to open the Code Editor. On the Edit menu, select Find and Replace, and then select Replace. Replace Form1 with frmcalculations using replace all.
In Solution Explorer, right-click the FrmCalculations.cs and select View Designer. The designer opens.
Add 5 Label controls, 4 Button controls, and a TextBox control to the form.
Set properties for these controls, as follows:
Control Name text
Label1 LblFactorial1 (blank)
Label2 LblFactorial2 (blank)
Label3 Lbladdtwo (blank)
Label4 Lblrunloops (blank)
Label5 lbltotalcalculations (blank)
Button1 btnFactorial1 Factorial
Button2 BtnFactorial2 Factorial-1
Button3 Btnaddtwo ADD Two
Button4 Btnrunloops Run a Loop
Textbox1 TxtValue (blank)
Creating Calculator Components
From the Project menu, choose Add Component.
Name the component Calculator.
To add a public variable to the Calculator component
Open the Code Editor for Calculator.
Add statements that create public variables that are used to pass values from frmcalculations to each thread.
The variable vartotalcalculations retains the cumulative value of the total number of calculations performed by the component, while the other variables receive the value from the form.
public int varaddtwo;
public int varFact1;
public int varFact2;
public int varloopvalue;
public double vartotalcalculations = 0;
Adding methods and events to the Calculator component
Declares delegates for events that the component will use to pass values to the form.
Note Although you will declare 4 events, because two of them will have the same signature, you only need to create 3 delegates.
Immediately below the variable declaration entered in the previous step, type the following code:
This delegate is 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);
Declares an event that the component will use to communicate with the application. To accomplish this, add the following code immediately below the code entered in the previous step.
public event Factorialcompletehandler Factorialcomplete;
public event Factorialcompletehandler Factorialminusonecomplete;
public event Addtwocompletehandler Addtwocomplete;
public event Loopcompletehandler Loopcomplete;
Immediately below the code you typed in the previous step, type the following code:
This method would 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 is this instant.
varTotalCalculations + 1;
varTotalAsOfNow = vartotalcalculations;
}
Signals that this method has completed, and communicates the
Result and a value of total calculations performed up to this
Point.
Factorialminusonecomplete (Varresult, varTotalAsOfNow);
}
This method would 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 would 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 would run a loop with a nested loop varloopvalue the Times.
public void Runaloop ()
{
int Varx;
Double varTotalAsOfNow = 0;
for (Varx = 1; varx <= varloopvalue; varx++)
{
This nested loop are added solely for the purpose of slowing down
The program and creating a processor-intensive application.
for (int varY = 1; varY <=; vary++)
{
varTotalCalculations + 1;
varTotalAsOfNow = vartotalcalculations;
}
}
Loopcomplete (varTotalAsOfNow, Varloopvalue);
}
Transferring user input to components
The next step is to add code to the frmcalculations to receive user input and to receive and transfer values from the Calculator component.
Realize the front-end function of frmcalculations
Open frmcalculations in the Code Editor.
Locate the public class frmcalculations statement. Immediately below {, type:
Calculator Calculator1;
Locate the constructor. Immediately before}, add the following line:
Creates a new instance of Calculator.
Calculator1 = new Calculator ();
Click each button in the designer, generate a code outline for each control's Click event handler, and add code to create the handlers.
When you are done, click an event handler to resemble the following:
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 ();
}
At the bottom of the code that you added in the previous step, type the following code to handle the events that the form will receive from Calculator1:
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 LoopDoneHandler (double calculations, int Count)
{
Btnrunloops.enabled = true;
Lblrunloops.text = Count.tostring ();
Lbltotalcalculations.text = "Totalcalculations are" +
Calculations.tostring ();
}
In the Frmcalculations constructor, add the following code before the} to handle the custom events that the 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);
Testing the Application
Now that the project has been created, the project will be able to combine components that perform several complex computations with the form. Although multithreading is not yet implemented, you should test your project before continuing to verify its functionality.
Test Project
From the Debug menu, choose Start.
The application starts and displays the frmcalculations.
Type 4 in the text box, and then click the button labeled "Add two".
The number "6" should be displayed in the label below the button, and "Total calculations are 1" should be displayed in lblTotalCalculations.
Now click the button labeled "Factorial 1".
The number "6" should be displayed below the button, and "Total calculations are 4" should now be displayed in lblTotalCalculations.
Change the value in the text box to 20, and then click the button labeled Factorial.
The number "2.43290200817664E+18" appears below the button, and the lblTotalCalculations is now displayed as "Total calculations are 24".
Change the value in the text box to 50000, and then click the button labeled Run loop.
Note that there is a brief but significant interval before this button is re-enabled. The label under this button should display "50000", while the total number of calculations is shown as "25000024".
Change the value in the text box to 5000000 and click the button labeled Run Loop, followed by the button labeled "Add two". Click it again.
The button and any controls on the form will not respond until the loop has finished.
If the program runs only a single thread of execution, the frequent use of the processor, similar to the example above, tends to occupy the program until the calculation is complete. In the next section, you will add multithreaded functionality to your application so that multiple threads can run at one time.
Add Multithreading Functionality
The preceding example shows the limitations of an application that runs only a single thread of execution. In the next section, you will use the thread class object to add multiple execution threads to the component.
Add Threads subroutine
Open Calculator.cs in the Code Editor.
Near the top of the code, locate the class declaration, and immediately below {, type the following code:
Declares the variables you'll 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;
At the bottom of the code, add the following methods before the end of the class declaration:
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 would 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
}
}
When the Thread object is instantiated, it requires an argument in the form of a ThreadStart object. The ThreadStart object is a delegate that points to the address of the method of the start thread. The ThreadStart object cannot accept parameters or pass values, so only the void method can be represented. The ChooseThreads method that you just implemented will receive a value from the program that called it, and use that value to determine the appropriate thread to start.
Add the appropriate code to the frmcalculations
Open the FrmCalculations.cs file in the Code Editor and find protected void btnFactorial1_Click.
Comment out the line that calls the Calculator1.factorial1 method directly, as follows:
Calculator1.factorial ()
Add the following line to invoke the Calculator1.choosethreads method:
Passes the value 1 to Calculator1, thus directing it to start the
Correct thread.
Calculator1.choosethreads (1);
Make similar modifications to other Button_Click subroutines.
Note Be sure to include the appropriate value for the Threads parameter.
When you are done, your code should look similar to the following form:
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 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);
}
Marshaling a call to a control
The display update on the form is now accelerated. Because the control is always owned by the main execution thread, any calls to the control in the subordinate thread require a "marshaling" call. Marshaling is the act of moving a call across thread boundaries, requiring a significant amount of resources. To minimize the amount of marshaling that needs to occur, and to ensure that calls are handled in a thread-safe manner, you should use the Control.BeginInvoke method to invoke methods on the master execution thread, minimizing the amount of marshaling that must occur across thread boundaries. This invocation is necessary when you invoke the method of manipulating the control. For more information, see manipulating controls from threads.
To create a control call procedure
Open the Code Editor for frmcalculations. In the Declarations section, add the following code:
public delegate void Fhandler (double Value, double calculations);
public delegate void A2handler (int Value, double calculations);
public delegate void Ldhandler (double calculations, int Count);
Invoke and BeginInvoke need to use the delegate of the appropriate method as an argument. These lines of code declare some delegate signatures that will be BeginInvoke used to invoke the appropriate method.
Add the following empty methods to your code.
public void Facthandler (double Value, double calculations)
{
}
public void Fact1handler (double Value, double calculations)
{
}
public void Add2Handler (int Value, double calculations)
{
}
public void LDoneHandler (double calculations, int Count)
{
}
On the Edit menu, use cut and paste to cut all the code from the FactorialHandler method and paste it into the facthandler.
Repeat the steps above for Factorialminushandler and Fact1handler, AddTwoHandler and Add2Handler, and LoopDoneHandler and LDoneHandler.
When you are done, there should be no remaining code in FactorialHandler, Factorial1Handler, AddTwoHandler, and LoopDoneHandler, and all the code that they once contained should have been moved to the appropriate new method.
Call the BeginInvoke method to call these methods asynchronously. You can call BeginInvoke from the form (this) or from any control on the form.
When you are done, your code should look similar to the following form:
protected void FactorialHandler (double Value, double calculations)
{
BeginInvoke causes asynchronous execution to begin in the address
specified by the delegate. Simply put, it transfers execution of
This method is 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});
}
protected void LoopDoneHandler (double calculations, int Count)
{
This. BeginInvoke (New Ldhandler (LDoneHandler), new object[]
{calculations, Count});
}
It seems as if the event handler is just calling the next method. In effect, the event handler implements the method called on the main operation thread. This approach saves calls across thread boundaries and enables multithreaded applications to run efficiently without fear of causing deadlocks. For more information about using controls in a multithreaded environment, see manipulating controls from threads.
Save your work.
Choose Start from the Debug menu to test the solution.
Type 10000000 in the text box and click Run Loop.
"Looping" is displayed in the label below this button. Running this loop should take a long time. If it finishes too quickly, adjust the size of the number accordingly.
Quickly click the three buttons that are still enabled. You will find that all the buttons respond to your input. The label under "Add two" should first display the result. The results are later displayed in the label below the factorial button. These results are estimated to be infinitely large, because the number returned by factorial 10,000,000 is too large for a double-precision variable to exceed the range it contains. Finally, in a few moments, the results are returned to the "Run Loop" button.
As has just been observed, four sets of independent computations are performed concurrently on four separate threads. The user interface maintains a response to the input and returns the result after each thread completes.
Coordinating threads
Experienced multi-threaded application users may find subtle flaws in the code they have typed. Recall the following line of code from each subroutine that performs the calculation from Calculator.cs:
varTotalCalculations + 1;
varTotalAsOfNow = vartotalcalculations;
The two lines of code increment the public variable vartotalcalculations and set the local variable varTotalAsOfNow to this value. The value is then returned to the frmcalculations and displayed in the Label control. But is the returned value correct? If only a single thread of execution is running, the answer is obviously correct. But if more than one thread is running, the answer becomes less certain. Each thread has the ability to increment the variable vartotalcalculations. It is possible that after a thread increments the variable, but before it copies the value to varTotalAsOfNow, another thread might change its value by incrementing the variable. This will lead to the possibility that each thread is actually reporting incorrect results. Visual C # provides lock statement statements to allow synchronization of threads, ensuring that each thread always returns accurate results. The syntax for lock is as follows:
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.
}
After the lock block is entered, execution of the specified expression is blocked until the specified thread has a private lock on the object being discussed. In the example shown above, the execution of AnObject is in a locked state. You must use lock on the object that returns the reference, not the object that returned the value. Then, the execution continues as a block without interference from other threads. The set of statements executed as a unit is called an "atom." When encountered}, the expression is freed and the thread continues to function correctly.
To add a lock statement to an application
Open Calculator.cs in the Code Editor.
Locate each instance of the following code:
varTotalCalculations + 1;
varTotalAsOfNow = vartotalcalculations;
There should be four instances of this code, one in each calculation method.
Modify this code so that it appears as follows:
Lock (This)
{
varTotalCalculations + 1;
varTotalAsOfNow = vartotalcalculations;
}
Save your work and test it as shown in the previous example.
You may notice subtle effects on program performance. This is because the execution of the thread stops when the component obtains an exclusive lock. Although it guarantees correctness, this approach offsets some of the performance benefits of multithreading. The need for locking threads should be carefully considered and implemented only when absolutely necessary.
The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion;
products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the
content of the page makes you feel confusing, please write us an email, we will handle the problem
within 5 days after receiving your email.
If you find any instances of plagiarism from the community, please send an email to:
info-contact@alibabacloud.com
and provide relevant evidence. A staff member will contact you within 5 working days.