C # multithreading User Guide ~~

Source: Internet
Author: User
Tags thread logic
C # multithreading User Guide ~~

I did not dare to share the articles I found on the Internet. I sent them for research !!
Net provides many multi-threaded programming tools, probably because there are too many tools, so there is always a headache to grasp. Here I will talk about some of my multi-threaded programming experiences, hoping to help you.

You do not need to pass parameters or return parameters.

We know that the most intuitive way to start a Thread is to use the Thread class. The specific steps are as follows:

ThreadStart threadStart = new ThreadStart (Calculate );
Thread thread = new Thread (threadStart );
Thread. Start ();

Public void Calculate (){
Double Diameter = 0.5;
Console. Write ("The perimeter Of Circle with a Diameter of {0} is {1}" Diameter, Diameter * Math. PI );
}

Example 1

We have defined a ThreadStart type delegate as above. This delegate develops the method to be executed by the thread: Calculate, in which the circumference of a circle with a diameter of 0.5 is calculated, and output. this constitutes the simplest example of multithreading. In many cases, this is enough, and the ThreadStart delegate is defined as void ThreadStart (), that is, the method to be executed does not have parameters. This is obviously a huge deficiency. To make up for this defect, a smart programmer has come up with many good methods, we will introduce it in the section that needs to pass multiple parameters. Here we will first introduce it. net another delegate set to solve this problem: ParameterizedThreadStart, which will be detailed below

A single parameter needs to be passed

ParameterThreadStart is defined as void ParameterizedThreadStart (object state )?? The startup function of the thread defined by this delegate can accept an input parameter. The example is as follows:
ParameterizedThreadStart threadStart = new ParameterizedThreadStart (Calculate)
Thread thread = new Thread ()
Thread. Start (0.9 );

Public void Calculate (object arg ){
Double Diameter = double (arg );
Console. Write ("The perimeter Of Circle with a Diameter of {0} is {1}" Diameter, Diameter * Math. PI );
}

Example 2
The Calculate method has a parameter of the object type. Although there is only one parameter and it is of the object type, type conversion is still required when using it, but fortunately there is a parameter, in addition, by combining multiple parameters into a class, and passing the instance of this class as a parameter, multiple parameters can be passed.

Multiple parameters need to be passed

Although multiple parameters can be transferred by entrusting ParameterizedThreadStart by encapsulating the required parameters into a class, the parameter conversion is inevitable because the input parameter of this delegate is an object, there are several common parameter passing methods below. Let's take a look at them one by one.

Use special thread classes

This is a classic mode that many programmers love to use. Simply put, they put the methods that need to be executed by another thread and the parameters they need into a class, and the parameters are used as the attributes of the class, declare the instance during the call, and then initialize the attributes. When the method is executed, use the initialized attributes in the class for execution, so that the method itself does not need parameters, the multi-parameter transfer effect is achieved, so we can use the ThreadStart delegation without parameters mentioned at the beginning of this article, and because the methods and parameters to be executed are put in a class, fully embodies the characteristics of object-oriented. the procedure is as follows:

We use a class to package the method for calculating the area. The input parameter Diameter (Diameter) is a field of this class.

Public class MyThread
{
Public double Diameter = 10;

Public double Result = 0;

Public MyThread (int Diameter)
{
This. Diameter = Diameter;
}

Public void Calculate ()
{
Console. WriteLine ("Calculate Start ");
Thread. Sleep (2000 );
Result = Diameter * Math. PI ;;
Console. WriteLine ("Calculate End, Diameter is {0}, Result is {1}", this. Diameter, Result );
}
}

MyThread t = new MyThread (5.0 );

ThreadStart threadStart = new ThreadStart (t. Calculate)
Thread thread = new Thread (threadStart );
Thread. Start ();

Example 3

In this way, the parameter transfer is changed to attribute sharing. It is good to encapsulate the data involved in the logic and the logic in terms of encapsulation, this method also has a clever variant that uses the anonymous method. This variant can be used to save independent classes. Now I am giving this method.

Double Diameter = 6;

Double Result = 0;
Thread ta = new Thread (new ThreadStart (delegate ()
{
Thread. Sleep (2000 );

Result = Diameter * Math. PI;
Console. WriteLine ("anonymous Calculate End, Diameter is {0}, Result is {1}", Diameter, Result );;
}));
Ta. Start ();

Example 4

This method is the same as in the preceding example. It changes the parameter transfer to the call to the variable, thus canceling the parameter transfer. However, the latter makes full use of a property of the anonymous method, you can directly use local variables in the current context, such as Diameter in the delegate, and Result. of course, the disadvantage of doing so is that if the anonymous method is too long, the program's readability will be reduced, so few people usually do this. Here we will provide this method for your reference, for more information about anonymous delegation, see

Smart readers must think that, since fields can be used to input variables, you can also use fields to output variables, for example, in the above two examples, we can see that the calculation results are written into a variable named Result (highlighted). Can we directly access this variable to get the calculation Result?

In this case, there is a fatal problem: Since it is asynchronous execution, how does the main thread know when the split thread completes the calculation? For example, in the above two examples, our threads are sleeping for 2000 milliseconds before calculation. If the main thread accesses the Result before the computation is completed, only one 0 value can be obtained. so we have a series of solutions below.

Parameters need to be passed and return parameters

As mentioned earlier, the main Thread needs to know when the sub-Thread will be executed. You can use the Thread. ThreadState enumeration to determine when the sub-Thread will be executed.

When the ThreadState of the thread is = ThreadState. when you Stop the job, it usually means that the thread has finished the job and the result is available. If it is not in this status, continue to execute other jobs, or wait for a while and try again. if we need to wait for multiple subthreads to return data and use their results for asynchronous computing, it is called thread synchronization, next, we will introduce another method that I recommend. It can customize the number of parameters and return data, which is relatively convenient to use.

Use delegate asynchronous call methods and callbacks

First, we need to define the method to be called asynchronously as a delegate, and then use BeginInvoke for asynchronous call. The first parameter of BeginInvoke is the diameter, the second is the method called after the thread is executed.

Delegate double CalculateMethod (double Diameter );

Static CalculateMethod calcMethod;
Double result = 0;

Static void Main (string [] args)
{

CalcMethod = new CalculateMethod (Calculate );

CalcMethod. BeginInvoke (5, new AsyncCallback (TaskFinished), null );

}

/// <Summary>
/// The function called by the thread
/// <Summary>

Public static double Calculate (double Diameter)
{
Return Diameter * Math. PI;
}

/// <Summary>
/// The callback function after the thread is completed
/// <Summary>
Public static void TaskFinished (IAsyncResult result)
{
Result = calcMethod. EndInvoke (result );
}

Example 5

Note: In the TaskFinished method executed after the thread execution is complete, we use EndInvoke to obtain the return value of this function.

Thread Pool

Although a thread is a good thing, it is also a large resource consumer. In many cases, we need to use multiple threads, but we do not want too many threads. This is the role of the thread pool ,. net provides a ready-made thread pool ThreadPool for us. Its usage is as follows:

WaitCallback w = new WaitCallback (Calculate );
ThreadPool. QueueUserWorkItem (w, 1.0 );
ThreadPool. QueueUserWorkItem (w, 2.0 );

ThreadPool. QueueUserWorkItem (w, 3.0 );
ThreadPool. QueueUserWorkItem (w, 4.0 );

Public static void Calculate (double Diameter)
{
Return Diameter * Math. PI;
}

Example 6

First define a WaitCallback delegate. The WaitCallback format is void WaitCallback (object state). That is to say, your method must conform to this format. Then call QueueUserWorkItem to add the task to the thread pool, when the county city pool has idle lines, your code will be scheduled and run.

Every process has a thread pool. The default thread pool size is 25. We can set its maximum value through the SetMaxThreads method.

[Note] Since each process has only one thread pool, if the thread pool is used in iis or sqlserver processes and the maximum capacity of the thread pool needs to be set, it will affect the iis process or SQL process, so be especially careful in both cases.

Control

When talking with everyone, I found that all colleagues who are used to object-oriented thinking are always troubled by the execution context in the multi-thread scenario. For example, in Example 5, the main program starts the subthread to execute the Calculate method, and calls back TaskFinished after execution. If the main thread id is 1, The subthread id is 2, so Calculate must be executed in the thread id = 2. What about the TaskFinished callback function? It is also executed in the context of the thread whose id is 2. If you do not believe it, try to output the thread id. This is usually not a problem, but when we need to use the subthread in Winform programming, the problem may occur. We will discuss the problem below.

Special features of multi-thread programming for form programs

When we move the callback code in Example 5 to winform, we can see the problem.

Public static void TaskFinished (IAsyncResult result)
{
Result = calcMethod. EndInvoke (result );
This. TextBox1.Text = result;
}

The purpose of the program is to write a TextBox into the result after the thread completes execution. However, when the program runs this. textBox1.Text = result. in the past, WinForm had strict requirements on threads. Apart from the threads used to create these controls, other threads do not allow cross-thread access to the properties and methods of controls on WinForm (except for a few special attributes). In some versions of the system, such as XP, this issue is handled, cross-thread control access can be executed, but most windows systems are not. If we do need to modify the control attributes across threads or call the control method, you must use the control method Invoke. This method can switch the execution context back to the thread for creating these controls. The specific operations are as follows:

Delegate void changeText (string result );

Public static void TaskFinished (IAsyncResult result)
{
Result = calcMethod. EndInvoke (result );

This. BeginInvoke (new changeText (this. textBox1.AppendText), t. Result. ToString ())
}

Because methods must be used in the delegate, I use the AppendTex method t instead of directly setting the Text attribute. If you want to set the text attribute, you must encapsulate a method and connect it to the delegate.
Let's introduce the classic asynchronous programming mode and Microsoft's implementation.

The Asynchronous Operation Model recommended by Microsoft is the event model, that is, the subthread notifies the caller of the working status through the event, that is, the observer mode in the design mode, it can also be seen as the extension of the thread class in the above text, and the call effect is similar

MyThread thread = new MyThread ()

Thread. Work + = new ThreadWork (Calculate)

Thread. WorkComplete + = new WorkComplete (DisplayResult)

Calculate (object sender, EventArgs e )){

....

}

DisplayResult (object sender, EventArgs e )){

...

}

<Example 1>

There have been many good articles on this topic.

As mentioned in the previous article, the problem of control is that the execution Context of the above model is a problem in winform. In callback functions (such as DisplayResult in <Example 1> ), we have to use BeginInvoke to call the properties and methods of controls created by the ui thread,

For example, in the above net66 example

// Create a thread object
_ Task = new newasynchui ();
// Hook progress bar modification event
_ Task. TaskProgressChanged + = new TaskEventHandler (OnTaskProgressChanged1 );

// Update the progress bar in the UI thread
Private void OnTaskProgressChanged1 (object sender, TaskEventArgs e)
{
If (InvokeRequired) // asynchronous call is not performed on the UI thread
{
TaskEventHandler TPChanged1 = new TaskEventHandler (OnTaskProgressChanged1 );
This. BeginInvoke (TPChanged1, new object [] {sender, e });
Console. WriteLine ("InvokeRequired = true ");
}
Else
{
ProgressBar. Value = e. Progress;
}
}

<Example 2>

We can see that the function uses

If (InvokeRequired)

{... BeginInvoke ....}

Else

{....}

This mode ensures that the methods can run under multiple threads and single threads. Therefore, the thread logic and interface logic are mixed together, so that the previous very simple task that only needs one sentence: progressBar. value = e. progress; it is very complicated. If the Thread class is provided as a public library and the requirement for writing events is relatively high, what is a better solution?

In fact, in. Net2.0, Microsoft implemented this mode and created the Backgroundworker class. He can solve the above problems. Let's take a look at his usage.

System. ComponentModel. BackgroundWorker bw = new System. ComponentModel. BackgroundWorker ();

// Define what needs to be done in the Child thread
Bw. DoWork + = new System. ComponentModel. DoWorkEventHandler (bw_DoWork );

// Define what needs to be done after execution
Bw. RunWorkerCompleted + = new System. ComponentModel. RunWorkerCompletedEventHandler (bw_RunWorkerCompleted );

// Start execution
Bw. RunWorkerAsync ();

Static void bw_RunWorkerCompleted (object sender, System. ComponentModel. RunWorkerCompletedEventArgs e)
{
MessageBox. Show ("Complete" + Thread. CurrentThread. ManagedThreadId. ToString ());
}

Static void bw_DoWork (object sender, System. ComponentModel. DoWorkEventArgs e)
{
MessageBox. Show (Thread. CurrentThread. ManagedThreadId );
}

<Example 3>

Note that I output the ID of the current thread in the two functions. When we execute the above Code in the WindowsForm program, we are surprised to find that, the callback function bw_RunWorkerCompleted is actually running in the UI thread. That is to say, in this method, we no longer need to use Invoke and BeginInvoke to call the control in winform. What's more strange to me is that, if the code is run in ConsoleApplication, the thread id output by bw_RunWorkerCompleted is different from the main thread id.

So how does BackgroundWorker implement cross-thread blocking?

After reading the code of this class, we find that it uses AsyncOperation. Post (SendOrPostCallback d, object arg)

Using this function in winform enables SendOrPostCallback to define the UI thread for being blocked and sent. Smart Bloggers can use this method to implement their own BackgroundWorker.

Continue to check and find that the key lies in the syncContext field of AsyncOperation. This is an object of the SynchronizationContext type, and the Post method of this object implements sending. When I continue to view

SynchronizationContext. Post method, which is simple and difficult to execute

Public virtual void Post (SendOrPostCallback d, object state)
{
ThreadPool. QueueUserWorkItem (new WaitCallback (d. Invoke), state );
}

How can this happen? The thread pool in this province does not have the ability to send messages by thread.
I think the program behavior in Winform and Console programs is different, and the Post method of SynchronizationContext is a virtual method. I guess this method may be overwritten by the class inherited from it.
By querying Msdn, we found that there are two sub-classes in this class, one of which is WindowsFormsSynchronizationContext. Let's take a look at the Post method of this class.
Public override void Post (SendOrPostCallback d, object state)
{
If (this. controlToSendTo! = Null)
{
This. controlToSendTo. BeginInvoke (d, new object [] {state });
}
}

Haha, it is also a familiar beginInvoke. The SynchronizationContext loaded by the console program and Winform program is different, so the behavior is different. Through a simple test, we can see that the console program directly uses the base class (SynchronizationContext), and The winform program uses the Post method of WindowsFormsSynchronizationContext to mail the method call to the control thread.

Summary:

This class also provides a progress change event that allows the user to terminate the thread with comprehensive functions. The thread pool is used internally, which can avoid resource consumption problems of a large number of threads in Chengdu, synchronizationContext solves the issue of sending messages, which makes the logic of our callback Event code simple and clear and is recommended for use.

Contact Us

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.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.