Multithreading in C #-getting started

Source: Internet
Author: User
Tags finally block

Overview and concepts
C # supports code execution in parallel through multiple threads. A thread has its own execution path and can run concurrently with other threads. A c # program starts with a single thread. This single thread is automatically created by CLR and the operating system (also known as the "main thread") and has multiple threads to create additional threads. Here is a simple example and its output:

Unless specified, all examples assume that the following namespace is referenced: using System; using System. Threading;

Class ThreadTest {
Static void Main (){
Thread t = new Thread (WriteY );
T. Start (); // run WriteY in the new thread
While (true) Console. Write ("x"); // keep writing 'X'
}
Static void WriteY (){
While (true) Console. Write ("y"); // keep writing 'y'
}
}
Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...

The main thread creates a new thread "t", which runs a method to repeatedly print the letter "y". At the same time, the main thread repeats but the letter "x ". CLR assigns each thread to its own memory stack to ensure the separation of local variables. In the following method, we define a local variable and call this method at the same time on the main thread and the newly created thread.

Static void Main (){
New Thread (Go). Start (); // call the Go () method in a new Thread
Go (); // call Go () in the main thread ()
}
Static void Go (){
// Declare and use a local variable 'cycle'
For (int cycles = 0; cycles <5; cycles ++) Console. Write ('? ');
}
??????????

The replicas of variable cycles are created in their respective memory stacks, and the output is the same. It is foreseeable that there will be 10 Question mark output. When threads reference some public target instances, they share data. The following is an example:

Class ThreadTest {
Bool done;
Static void Main (){
ThreadTest tt = new ThreadTest (); // create an instance
New Thread (tt. Go). Start ();
Tt. Go ();
}
// Note that Go is an instance method.
Void Go (){
If (! Done) {done = true; Console. WriteLine ("Done ");}
}
}
In the same ThreadTest instance, both threads call Go () and share the done field. This result outputs a "Done" instead of two.

Done

Static fields provide another way to share data between threads. The following example uses done as a static field:

Class ThreadTest {
Static bool done; // The static method is used by all threads.
Static void Main (){
New Thread (Go). Start ();
Go ();
}
Static void Go (){
If (! Done) {done = true; Console. WriteLine ("Done ");}
}
}
The two examples above are sufficient to illustrate another key concept, namely thread security (or vice versa, its shortcomings! ) The output is actually uncertain: It is possible (though unlikely), and "Done" can be printed twice. However, if we change the order of commands in the Go method, the chances of "Done" being printed twice will increase significantly:

Static void Go (){
If (! Done) {Console. WriteLine ("Done"); done = true ;}
}
Done (usually !)

The problem is that when a thread judges the if block, the other thread is executing the WriteLine statement -- before it sets done to true.

The remedy is to provide an exclusive lock when reading and writing public fields; C # provides a lock statement to achieve this purpose:

Class ThreadSafe {
Static bool done;
Static object locker = new object ();
Static void Main (){
New Thread (Go). Start ();
Go ();
}
Static void Go (){
Lock (locker ){
If (! Done) {Console. WriteLine ("Done"); done = true ;}
}
}
}
When two threads compete for a lock (in this example, locker), one thread waits, or is blocked to the available lock. In this case, only one thread can enter the critical section at the same time, so "Done" is printed only once. Code is called thread security in an uncertain multi-threaded environment in this way.

Temporary pause or stop is the essential feature of multi-thread collaboration and synchronization activities. Waiting for an exclusive lock to be released is the reason why a thread is blocked. Another reason is that the thread wants to pause or Sleep for a period of time:

Thread. Sleep (TimeSpan. FromSeconds (30); // block 30 seconds
A thread can also use its Join method to wait for the end of another thread:

Thread t = new Thread (Go); // assume that Go is a static method.
T. Start ();
T. Join (); // wait (Block) until thread t ends
Once a thread is blocked, it no longer consumes CPU resources.

How does a thread work?
A thread is managed by a thread coordinator-a function that the CLR delegates to the operating system. The thread coordinator ensures that all active threads are allocated an appropriate execution time; and those threads that are waiting or blocked-for example, in the exclusive lock or in user input-do not consume CPU time.

In a single-core processor computer, the thread Coordinator completes a time slice and quickly switches between active threads for execution. This leads to Choppy behavior. For example, in the first example, the repeated X or Y blocks are equivalent to the time slice allocated to the thread. In Windows XP, the time slice usually consumes more time within 10 milliseconds than the CPU overhead during thread switching. (Usually in a few microseconds)

In a multi-core computer, multithreading is implemented as a hybrid time slice and real concurrency-different threads run on different CPUs. This almost certainly still produces some time slice, because the operating system needs to serve its own thread, as well as some other applications.

A thread is called a preemptible because of the interruption of external factors (such as time slices). In most cases, a thread loses control of it at the moment it is preemptible.

Thread vs. Process
All threads of a single application are logically included in a process. A process refers to the operating system unit that an application runs.

Threads are similar to processes. For example, a process usually runs in a time slice and other processes running on a computer in the same way as a C # program thread. The key difference between the two lies in that processes are completely isolated from each other. The thread shares the heap memory with other threads running in the same program, which is why the thread is so useful: A thread can read data in the background, another thread can display the data that has been read in the previous stage.

When to use multithreading
Multi-threaded programs are generally used to execute time-consuming tasks in the background. The main thread keeps running and the working thread does its background work. For Windows Forms programs, if the main thread tries to perform lengthy operations, the keyboard and mouse operations will become dull and the program will lose response. For this reason, you should add a working thread when running a time-consuming task in the working thread, even if there is a good prompt on the main thread that "processing... to prevent the work from continuing. This avoids errors caused by "No response" prompted by the operating system to entice the user to forcibly terminate the process of the program. The Mode dialog box also allows the "cancel" function to continue receiving events, and the actual task has been completed by the Working thread. BackgroundWorker can help with this function.

In a program without a user interface, for example, Windows Service and multithreading, when a task has potential time consumption, it is waiting for the response from another computer (such as an application server, database server, or a client. Using a working thread to complete a task means that the main thread can do other tasks immediately.

Another purpose of Multithreading is to complete a complex computing task in a method. This method runs faster on a multi-core computer. If the workload is separated by multiple threads (use the Environment. ProcessorCount attribute to detect the number of chips processed ).

A c # program is called multithreading in two ways: explicitly creating and running multithreading, or using. NET framework secretly uses the characteristics of multithreading-such as BackgroundWorker class, thread pool, threading timer, remote server, or Web Services or ASP.. NET program. In later cases, people have no choice but to use multithreading; A single-threaded ASP. NET web server is not so cool, even if there is such a thing; fortunately, multithreading in the application server is quite common; the only concern is the static variable issue that provides an appropriate lock mechanism.

When do not use multithreading
Multithreading also brings disadvantages. The biggest problem is that it makes the program too complex, and multithreading itself is not complicated. complexity is the interaction of threads, this brings about a long development cycle and intermittent and non-repetitive bugs regardless of whether interaction is intentional or not. Therefore, the multi-thread interaction design is simpler or the multi-thread interaction is not used at all. Unless you have a strong desire for rewriting and debugging.

When users frequently allocate and switch threads, multithreading will increase resource and CPU overhead. In some cases, too many I/O operations are very tricky. When there are only one or two working threads, there are more than many threads that execute task blocks at the same time. Later we will implement the producer/consumer queue, which provides the above features.

Create and start Multithreading
The Thread is created using the Thread class, And the ThreadStart delegate is used to specify where the method starts to run. The following is how the ThreadStart delegate defines it:

Public delegate void ThreadStart ();
After the Start method is called, the thread starts to run until the return of the method called by the thread ends. The following example uses the C # syntax to create TheadStart delegation:

Class ThreadTest {
Static void Main (){
Thread t = new Thread (new ThreadStart (Go ));
T. Start (); // run Go () in the new thread ()
Go (); // run Go () in the main thread at the same time ()
}
Static void Go () {Console. WriteLine ("hello! ");}
In this example, the thread t executes the Go () method and the main thread also calls Go (). The result is that two hello messages are printed almost simultaneously:

Hello! Hello!

A thread can be created more conveniently through the short syntax of the C # heap delegate:

Static void Main (){
Thread t = new Thread (Go); // There is no need to explicitly use ThreadStart
T. Start ();
...
}
Static void Go (){...}
In this case, ThreadStart is automatically inferred by the compiler. Another quick way is to use the anonymous method to start the thread:

Static void Main (){
Thread t = new Thread (delegate () {Console. WriteLine ("Hello! ");});
T. Start ();
}
The thread has an IsAlive attribute, which is always true after Start () is called until the end of the thread.

Once a thread ends, it cannot start again.

Pass data to www.2cto.com in ThreadStart
In the above example, we want to better distinguish the output results of each thread and let one thread output uppercase letters. We input a state word to Go to complete the entire task, but we cannot use ThreadStart to delegate because it does not accept the parameter. Fortunately ,. NET framework defines another version of the delegate called ParameterizedThreadStart, which can receive a separate object type parameter:

Public delegate void ParameterizedThreadStart (object obj );
The previous example looks like this:

Class ThreadTest {
Static void Main (){
Thread t = new Thread (Go );
T. Start (true); // = Go (true)
Go (false );
}
Static void Go (object upperCase ){
Bool upper = (bool) upperCase;
Console. WriteLine (upper? "HELLO! ":" Hello! ");
}
Hello! HELLO!

In the entire example, the compiler automatically deduce the ParameterizedThreadStart delegate, because the Go method receives a separate object parameter and writes it like this:

Thread t = new Thread (new ParameterizedThreadStart (Go ));
T. Start (true );
ParameterizedThreadStart is a feature that requires packing of the desired type (bool here) before use, and it can only receive one parameter.

An alternative solution is to use an anonymous method to call a common method as follows:

Static void Main (){
Thread t = new Thread (delegate () {WriteText ("Hello ");});
T. Start ();
}
Static void WriteText (string text) {Console. WriteLine (text );}
The advantage is that the target method (WriteText here) can receive any number of parameters without packing. However, you need to put an external variable into the anonymous method, as shown below:

Static void Main (){
String text = "Before ";
Thread t = new Thread (delegate () {WriteText (text );});
Text = "After ";
T. Start ();
}
Static void WriteText (string text) {Console. WriteLine (text );}
After

The anonymous method opens up a strange phenomenon. When an external variable is modified by a later part of the variable, it may be inadvertently interacted through the external variable. Intentional interactions (usually through fields) are considered sufficient! Once the thread starts running, it is recommended that the external variables be processed as read-only-unless someone is willing to use the appropriate lock.

Another common method is to pass the object instance method instead of the static method into the thread. The attributes of the object instance can tell the thread what to do, as shown in the following example:

Class ThreadTest {
Bool upper;
Static void Main (){
ThreadTest instance1 = new ThreadTest ();
Instance1.upper = true;
Thread t = new Thread (instance1.Go );
T. Start ();
ThreadTest instance2 = new ThreadTest ();
Instance2.Go (); // main thread -- run upper = false
}
Void Go () {Console. WriteLine (upper? "HELLO! ":" Hello! ");}
Naming thread
A thread can be named by its Name attribute, which is not conducive to debugging: You can use the Console. writeLine prints the thread name. Microsoft Visual Studio can display the thread name on the debugging toolbar. The thread name can be set at any time-but only once. renaming will cause an exception.

The main thread of the program can also be named. In the following example, the main thread is named by CurrentThread:

Class ThreadNaming {
Static void Main (){
Thread. CurrentThread. Name = "main ";
Thread worker = new Thread (Go );
Worker. Name = "worker ";
Worker. Start ();
Go ();
}
Static void Go (){
Console. WriteLine ("Hello from" + Thread. CurrentThread. Name );
}
}
Hello from main Hello from worker

Foreground and background threads
The default thread is the foreground thread, which means that any foreground thread will keep the program alive during running. C # also supports background threads. When all foreground threads end, they do not maintain program survival.

Changing the priority and status of a thread from the foreground to the background does not change in any way in the CPU Coordination Program.

The IsBackground attribute of a thread controls the frontend and backend states, as shown in the following example:

Class PriorityTest {
Static void Main (string [] args ){
Thread worker = new Thread (delegate () {Console. ReadLine ();});
If (args. Length> 0) worker. IsBackground = true;
Worker. Start ();
}
}
If the program is called without any parameters, the working thread is the foreground thread, and the ReadLine statement will wait for the user to trigger the carriage return. During this period, the main thread exits, but the program remains running, because a foreground thread is still alive.

On the other hand, if a parameter is passed into Main (), the working thread is assigned a value to the background thread. When the Main thread ends the program, it immediately exits and stops ReadLine.

This method of terminating the background thread avoids any final operation. This method is not suitable. The good way is to explicitly wait for any background working Thread to finish the program and then end the program. A timeout may be used (mostly Thread. Join ). If a worker thread cannot be completed for some reason, try to terminate it. If it fails, discard the thread and allow it to die with the process. (Record is a difficult problem, but it makes sense in this scenario)

It is helpful to have a background working thread. The most direct reason is that when it comes to the end program, it may always have the last say. The front-end thread is intertwined to ensure the normal exit of the program. It is especially dangerous to discard a front-end working thread, especially for Windows Forms programs, because the program does not exit until the main thread ends (at least for the user), but its process is still running. In Windows Task Manager, it disappears from the application bar, but can be found in the Process bar. Unless the user finds and ends it, it will continue to consume resources and may block a new instance from running or affecting its features.

The common cause of program exit failure is that there is a "forgotten" front-end thread.

Thread priority
The Priority attribute of the thread determines the execution time of the thread relative to the activity of the same process. The following levels are:

Enum ThreadPriority {Lowest, BelowNormal, Normal, AboveNormal, Highest}
Priority is valid only when multiple threads are active at the same time.

Setting a thread with a higher priority does not mean it can execute real-time work because it is limited by the program process level. To perform real-time work, you must upgrade the Process level in the System. Diagnostics namespace, as shown below: (I didn't tell you how to do this)

Process. GetCurrentProcess (). PriorityClass = ProcessPriorityClass. High;
ProcessPriorityClass. High is actually the highest priority in the process of a short gap: Realtime. Set the process level to Realtime to notify the operating system: You don't want your process to be preemptible. If your program enters an accidental endless loop, it can be expected that the operating system is locked, and there is nothing to save you except shutdown! Based on this, High is generally considered the highest level of useful processes.

If a real-time program has a user interface, improving the process level is not very good, because when the UI of the user interface is too complex, updating the interface consumes too much CPU time, slowed down the entire computer. (Although Skype, the Internet phone program, was lucky to do this when I wrote this article, maybe it is because its interface is quite simple .) Reduce the level of the main thread, improve the process level, and ensure that the Real-Time Thread does not refresh the interface, but this does not prevent the computer from getting slower and slower, because the operating system will still allocate too much CPU to the entire process. The ideal solution is to enable real-time operations and user interfaces to run in different processes (with different priorities) and communicate through Remoting or shared memory, shared Memory requires P/Invoking in Win32 API. (You can search for CreateFileMapping and MapViewOfFile)

Exception Handling
Any thread creates a try/catch/finally block within the scope. When the thread starts to execute, it no longer has any relationship with it. Consider the following program:

Public static void Main (){
Try {
New Thread (Go). Start ();
}
Catch (Exception ex ){
// No exception will be returned here
Console. WriteLine ("Exception! ");
}
Static void Go () {throw null ;}
}
Here, try/catch statements are not used at all. The newly created thread will cause NullReferenceException. When you consider that each thread has an independent execution path, you will know that this behavior is justified. The remedy is to add their own exception handling in the thread processing method:

Public static void Main (){
New Thread (Go). Start ();
}
Static void Go (){
Try {
...
Throw null; // This exception will be caught below
...
}
Catch (Exception ex ){
Record exception logs and notify another thread
An error occurred.
...
}
From. NET 2.0, unprocessed exceptions in any thread will cause the entire program to close, which means ignoring exceptions is no longer an option. Therefore, to avoid program crashes caused by Unprocessed exceptions, try/catch blocks must appear in the method entered by each thread, at least in the Product Program. For Windows Forms programmers who often use "global" exception handling, this may be a bit troublesome, as shown below:

Using System;
Using System. Threading;
Using System. Windows. Forms;
Static class Program {
Static void Main (){
Application. ThreadException + = HandleError;
Application. Run (new MainForm ());
}
Static void HandleError (object sender, ThreadExceptionEventArgs e ){
Record exceptions, exit the program, or continue running...
}
}
Application. threadException is triggered when an exception is thrown. Simply put, the ThreadException event is triggered by a Windows message (for example, the keyboard and mouse Animation "paint, almost all the code of a Windows Forms Program. Although this looks perfect, it creates a false sense of security-all exceptions are caught by central exception handling. The exception thrown by the worker thread is a good exception that is not caught by Application. ThreadException. (The code in the Main method, including the constructor form, is executed before the start of Windows Information)

. NET framework provides a lower-level event for global Exception Handling: AppDomain. unhandledException, this event is triggered in any thread of any type of program (with or without a user interface) with any unprocessed exception. Although it provides a forced exception handling mechanism, this does not mean that the program will not crash or cancel the. NET exception dialog box.


From SYZ_YUMEIZHOU_YY

Related Article

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.