Thread (1) in C # getting started

Source: Internet
Author: User

 

ArticleSystem reference reprinted, English original URL, please refer to: http://www.albahari.com/threading/

Author Joseph albahari, Translation Swanky Wu

The Chinese translation author put the original article on "Google collaboration", and GFW is blocked and cannot be accessed or viewed. Therefore, I organized and reproduced it in the garden according to the original translation and English version.

This series of articles can be regarded as a very good C # thread manual with clear thinking and key points. After reading it, I have a deeper understanding of the thread and synchronization of C.

    • Getting started

      • Overview and concepts
      • Create and start Multithreading
    • Thread Synchronization Basics
      • Essentials of Synchronization
      • Lock and thread security
      • Interrupt and abort
      • Thread status
      • Waiting handle
      • Synchronization Environment
    • Multithreading
      • Unit mode and Windows Forms
      • Backgroundworker class
      • Readerwriterlock class
      • Thread Pool
      • Asynchronous Delegation
      • Timer
      • Local Storage
    • Advanced topic
      • Non-blocking Synchronization
      • Wait and pulse
      • Suspend and resume
      • Terminate thread

I. Getting Started

1. Overview and concepts

C # supports parallel execution through multiple threadsCodeA thread has its own execution path, which can run concurrently with other threads. A c #ProgramStarting from 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 on the new thread while (true) console. write ("X"); // write 'X' forever} static void writey () {While (true) console. write ("Y"); // write 'y' forever }}

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 go () on a new thread go (); // call go () on the main thread} static void go () {// declare and use a local variable-'cycles 'For (INT cycles = 0; cycles <5; cycles ++) console. write ('? ');}

VariableCyclesThe copies are created in their respective memory stacks, and the output is also the same. It is foreseeable that there will be 10 question marks for 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 a common instance new thread (TT. go ). start (); TT. go ();} // note that go is now an instance method void go () {If (! Done) {done = true; console. writeline ("done") ;}} becauseThreadtestIn the instance, both threads callGo (), They are sharedDoneField, which outputs a "done" instead of two.
 
 

 

Static fields provide another way to share data between threads.DoneExample of static fields:

Class threadtest {static bool done; // static fields are shared between 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 isThread Security(Or vice versa, its shortcomings! ) The output is actually uncertain: It is possible (though unlikely), and "done" can be printed twice. However, if weGoThe order of commands in the method is changed. The chances of "done" being printed twice are greatly increased:

 
Static void go () {If (! Done) {console. writeline ("done"); done = true ;}}

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 provideExclusive lockC # providesLockStatement to achieve this goal:

 
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, It is locker), one thread waits, or isBlockTo 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 in an uncertain multi-threaded environment in this wayThread Security.

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 orSleepFor a period of time:

Thread. Sleep (timespan. fromseconds (30); // block for 30 seconds

A thread can also use its join method to wait for the end of another thread:

 
Thread t = new thread (GO); // assume go is some static methodt. 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.BackgroundworkerThis function can be assisted.

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 called multithreading can be explicitly created and run in two ways, or the. NET Framework secretly uses the features of multithreading-for exampleBackgroundworkerClass,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.

 

2. Create and start using 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 () on the new thread. go (); // simultaneously run go () in the main thread .} 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:

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

 
Static void main () {thread t = new thread (GO); // 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 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! ");}

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 );}

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 );}}

  

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. A good way is to explicitly wait for any background worker thread to finish the program and then end the program. A timeout may be used.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 it.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) {// The exception 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 the exception log, or notify another thread of an error ...}

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 or exit the program or continue running ...}}

 

Application. threadexceptionAn event is triggered when an exception is thrown. in simple words, the event is triggered by a Windows message (for example, the keyboard, or the mouse "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.

In the product program, it is necessary to explicitly use exception handling in the methods that all threads enter. You can use the packaging class and help class to break down the work to complete the task, such as usingBackgroundworkerClass (discussed in the third part)

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.