thread

The Thread class is in the System.Threading assembly. When learning about threads, you need to understand the state transitions between threads and the following methods provided by threads:

  • Thread state transition diagram:







Start: The operating system changes the current instance status to Running. (Running) Join: Waits for the thread represented by this instance to terminate. Sleep: Suspends execution of the current thread for a specified time. Suspend: Suspends a thread, or does not take effect if the thread is already suspended. Resume: Resumes a thread that has been suspended.

Create a thread

The constructor takes a delegate representing the action to be performed by the thread. The implementation of the delegate calls two constructors with no arguments:

  • A constructor with no arguments
// Create and start a thread
Thread t1 = new Thread(MyFuction);
t1.Start();

/ / MyFuction function body
static void MyFuction(Object a)
{
    for(int i = 0; i <100; i++)
    {
        Console.Write("2"); }}Copy the code
  • A constructor with arguments
static void Main(string[] args)
{
	// Create a thread and pass in parameters
    Thread t = new Thread(MyFuction);
    t.Start(10);
    for(int i = 0; i < 100; i++)
    {
        Console.Write("1"); }}// Parameter declarations for specific functions
static void MyFuction(Object a)
{
    for(int i = 0; i <100; i++)
    {
        Console.Write("2"); }}Copy the code

Pay attention toThe parameter type passed by the creation thread can only be declared asObjectAnd in theStartMethod to pass in the parameters. The running results are shown as follows:



Pass data to the thread

In addition to wrapping parameters in Object objects and passing them to Thread objects, Lambda expressions are more concise and efficient:

static void Main()
{
    Thread t = new Thread( () => {
    	MyFuction(prama1,prama2);
    });
}
static void MyFuction(object prama1,object prama2)
{
    // Business logic. }Copy the code

In contrast, using Lambda expressions actually calls other parameterized methods in a method to pass values, whereas using constructors requires wrapping all parameters into an Object.

Pay attention to

Do not arbitrarily change the value of a parameter variable after the thread has started. For example:

for(int i = 0; i <10; i++) {new Thread(() => {
    	Console.Write(i)
    }).Start();
}
Copy the code

The result may not be 0123456789, but 0444555999. The reason is that the argument I passed in when the line is created is at the same memory address for the entire cycle, and the ten threads created are all accessing the same address when performing specific printing tasks.

Local status and shared status

The local state

The CLR allocates a separate memory stack for each thread to ensure the isolation of local variables.

static void Main()
{
    new Thread(MyFuction).Start();
    MyFuction();
}
static void MyFuction()
{
	for(int i = 0; i <10; i++) Console.Write("1");
}
Copy the code

The code above prints 20 ones because the variable I is isolated between the two threads.

Shared state

The shared state is caused by multiple threads using the same reference. This can happen in one of the following ways:

  • Lambda expressions convert captured variables into fields
static void Main()
{
    bool _done = false;
    ThreadStart myFuction = () => {
        if(! _done){ _done =true;
            Console.Write("Done"); }};new Thread(myFuction).Start();
    myFuction();
}
Copy the code

Done is printed only once, not twice, because Lambda expressions convert _done to fields, causing both threads to use the same _done.

  • Multiple threads use a reference to the same instance object, and the object’s data is shared by multiple threads
class ThreadTest
{
    bool _done;
    static void Main(){
        // Create a shared instance
        Thread t = new ThreadTest();

        newThread(myFuction).Start(); myFuction(); }}Copy the code
  • Multiple threads share static fields
class ThreadTest
{
    // The shared static field
    static bool _done;
    static void Main(){
        newThread(myFuction).Start(); myFuction(); }}Copy the code

Sharing data can lead to unsafe operations. When multiple threads share variables, the above methods may print Done twice.

Locking and thread safety

Basic handling of data sharing across multiple threads: C# lock statement

class ThreadTest
{
    static bool _done;
    / / exclusive lock
    static readonly object _locker = new object(a);static void Main(){
        new Thread(myFuction).Start();
        myFuction();
    }

    static void myFuction(){
        / / exclusive lock
        lock (_locker)
        {
            if(! _done){ Console.Write("Done");
            _done = true; }}}}Copy the code

When two threads compete for locked resources, one thread will wait for the other thread to release resources to ensure data security.

Exception handling in threads

Thread execution is independent of the exception handling process that creates the thread, for example:

static void Main()
{
    try{
        new Thread(myFuction).Start();
    }catch{
        Console.Write("Catch exception"); }}static void myFuction(){
    // Throw a null-reference exception
    throw null;
}
Copy the code

The above example never catches an exception, and the newly created thread is still affected by null-reference exceptions. The normal way is to put the exception handling process into the myFuction method.

Foreground program and background program

The IsBackground property in the Thread class marks whether a Thread is a daemon.

  • Shows that the created thread is a foreground thread, that isIsBackgroundAttribute is his;
  • As long as one foreground thread is running, the application remains running.
  • When all foreground threads terminate, the application is stopped and all running background threads are terminated.

For example:

static void Main(string[] args)
{
    Thread thread = new Thread(() =>
    {
        Console.ReadLine();
    });
    if (args.Length > 0)
    {
        thread.IsBackground = false;
    }
    thread.Start();
}
Copy the code

When the program is run from the command line, the dotnet run thread is in the foreground state, waiting for user input without ending. If the application starts with parameters, the worker thread is set to background state and the application exits at the end of the main thread, terminating ReadLine execution.