When to use threads

   In fact, all programs are executed in a thread, so understanding how .NET and Windows execute threads will help to understand how the program will execute at runtime. Windows Form applications use event loop threads to handle user interface interactions. Each form is executed on a different thread. If you need to interact between Windows Forms, you need to interact between threads. ASP.NET pages execute in the multi-threaded environment of IIS. Different requests on the same page can be in different threads. On the execution, the same page can be executed in multiple threads at the same time. A threading issue occurs when accessing a shared resource on an ASP.NET page.

Thread

  Thread technology refers to the development architecture to divide the application part into “threads”, so that the thread and the rest of the program are executed in different order. In the vast programming language, there is a method equivalent to Main(), each line in the method. It must be executed in order. The next line of code is executed after the previous line of code is executed. Thread is a special condition that is part of the operating system’s execution of multitasking. It allows a part of the application to be executed independently of other objects. So out of the normal execution order

process

  When the application is launched, the system automatically allocates memory and other related resources to the system. The physical separation of memory and resources is called a process. Of course, the application can start multiple processes. The “application” and the “application” are remembered. “Process” is not synonymous. The memory allocated to the process is quarantined for the memory allocated by other processes, only the process to which it belongs can access it, so each process should have a thread!

  In Windows, by accessing the Windows Task Manager, you can view the currently running process very intuitively. Right click on the blank option in the taskbar to see it.

NET and C# support for threads

  .NET supports free threads. All languages ​​that are .NET can use threads, including C# and VB.NET, and F#. As mentioned earlier, “process” is a physically separate part of memory and resources. Later it was mentioned that a process has at least one thread. When MicroSoft designed the .NET Framework, it added a layer of isolation called the application domain or AppDomain. The application does not exist as a process, but rather a logically separate part of the process. There can be multiple program domains in a process. This is a very good advantage. Usually, standard processes cannot access data of other processes without using a proxy. Using a proxy is very expensive and the coding is complicated, but as we introduce the concept of a program domain, multiple applications can be launched in one process. The quarantine provided by the process can be used with the application, the thread can be executed across multiple applications, and the system overhead caused by the internal allocation of the thread does not need to be consumed. Another advantage is that they provide type checking. Features.

   Microsoft encapsulates all the functionality of these application domains in a class called System, AppDomain. Microsoft.NET assemblies have a very close relationship with these application domains, as long as the assembly is loaded into the application, it The AppDomain will be loaded. Unless otherwise noted, the assembly will be loaded into the AppDomain of the calling code. Application domains are also directly related to threads. They can hold one or more threads, just like processes. The difference is that the application domain can be created within the process, but not a new thread.  

Define thread

After learning about the theory and model, let’s introduce some actual code. The following example will use AppDomain to set the data, retrieve the data, and represent the thread in which the AppDomain is executing, creating an appdomain.cs.

/ / Define the application domain
        Public AppDomain Domain;
        Public int ThreadId;
        //Settings
        Public void SetDomainData(string vName,string vValue)
        {
            Domain.SetData(vName,(object )vValue);
            ThreadId = AppDomain.GetCurrentThreadId();
        }
        / / Get the value
        Public string GetDomainData(string name)
        {
            Return (string )Domain.GetData(name);
        }
        Public static void DomainMainGo()
        {
            String DataName = "MyData" ;
            String DataValue = "Some Data to be stored" ;

            Console.WriteLine("Retrieving current domain" );
            MyAppDomain Obj = new MyAppDomain();
            Obj.Domain = AppDomain.CurrentDomain;
                
            Console.WriteLine("Setting domain data" );
            Obj.SetDomainData(DataName, DataValue);

            Console.WriteLine("Getting domain data" );
            Console.WriteLine($"The Data found for key{DataName}, is{Obj.GetDomainData(DataName)}, running on thread id: {Obj.ThreadId}" );
        }

This is also very simple for C# developers who have no experience. Let’s take a look at the code and explain what happened. The first important code in this class is as follows:

Public void SetDomainData(string vName,string vValue)
        {
            Domain.SetData(vName,(object )vValue);
            ThreadId = AppDomain.GetCurrentThreadId();
        }
The parameters of this method are the name of the data to be set and its value. Note that when the SetData() method passed in the parameters, something very different was done. This casts a value of type String to the object data type because the SetData() method takes the object data type as its second argument. This method uses only one String, and String is derived from System.Object, so you don’t have to cast this variable to the object data type. However, to store other data, it is not so easy to handle, just to remind you of this conversion. At the end of this method, you can get the currently running ThreadId by calling the GetCurrentThreadId property of the AppDomain object.
  Let me talk about this method of Get.
Public string GetDomainData(string name)
        {
            Return (string )Domain.GetData(name);
        }

This method is very simple, here use the GetData () method of the AppDomain class to get the data according to the key value. Pass the argument from the GetDomainData method to GetData, and pass the result of the method back to the calling method.

Thread in NET

Just said what the thread is, introduced a lot of basic knowledge, and some important concepts, then let’s talk about threads in .NET. Speaking of this, you must have thought of Systeam.Threading. There are not many attributes or method tables listed here, too many.

Here is a simple chestnut, which is not suitable for explaining why threads are used, but removes all the complications mentioned later, creating a new console application and naming the file simple_thread.cs.

Public class simple_thread
    {
        Public void SimpleMethod()
        {   
            Int i = 5 ;
            Int x = 10 ;
            Int result = i * x;
            Console.WriteLine($"This code calculated the value {result.ToString()} from threadID:{AppDomain.GetCurrentThreadId().ToString()}" );
        }
        Public static void MainGo()
        {
            Simple_thread simple = new simple_thread();
            simple.SimpleMethod();

            ThreadStart ts = new ThreadStart(simple.SimpleMethod);
            Thread t = new Thread(ts);
            t.Start();
        }
    }

Now for a brief explanation of this simple code, the functionality of the thread is encapsulated in the System.Threading namespace, so this namespace must be imported into the project. Once this namespace is imported, you can create a method that can be executed on the main thread and on the new worker thread. When you execute the SimpleMethod method code for the second time, it is actually executed on another thread.

In the above example, we can’t explain anything because we can’t display different thread IDs, after all, we haven’t executed multiple threads yet. To simulate higher realism, we create a class called do_something_thread.cs, which is defined as follows:

Public class DoSomethingThread
    {
        Static void WorkerMethod()
        {
            For (int i=1;i<1000;i++ )
            {
                Console.WriteLine("Worker Thread:"+ i.ToString());
            }
        }
        Static void MainGo()
        {
            ThreadStart ts = new ThreadStart(WorkerMethod);
            Thread t = new Thread(ts);
            t.Start();
            For (int i=0;i<1000;i++ )
            {
                Console.WriteLine("Primary Thread"+ i.ToString());
            }
        }
    }

Because these codes don’t involve new coding techniques, they are not covered here. However, it can be seen that the execution time is shared between the two threads. Before a thread completes, another thread does not stop completely. Each thread will have a short time to execute. After a thread has run out of its execution time, the next thread starts executing in its time slice. These two threads will continue to do this alternately until the execution is complete. In fact, there are more than two threads on the system that are alternately executing, sharing time slices. In the previous application, it was not just switching back and forth between two threads. In fact, the application’s thread shares execution time with many threads that are currently running on the computer.
Now look back at the ThreadStart delegates and use these delegates k to do some interesting things. For example, we have all done the day after tomorrow permission management, we will have to implement a different role to log in to the background, then there will be different scenarios, listed in the administrator login, we must run a background process to collect report data. When the report is completed, ji informs the administrator, and for ordinary users, this feature is not needed. This is the object-oriented feature of ThreadStart. Below we create a ThreadStartBranching.cs.

Public class ThreadStartBranching
    {
        enum UserClass
        {
            ClassAdmin,
            ClassUser
        }
        Static void AdminMethod()
        {
            Console.WriteLine("Admin Method" );
        }
        Static void UserMethod()
        {
            Console.WriteLine("User Method" );
        }
        Static void ExecuteFor(UserClass uc)
        {
            ThreadStart ts;
            ThreadStart tsAdmin = new ThreadStart(AdminMethod);
            ThreadStart tsUser = new ThreadStart(UserMethod);
            If (uc == UserClass.ClassAdmin)
                Ts = tsAdmin;
            Else 
                ts = tsUser;
            Thread t = new Thread(ts);
            t.Start();
        }
       Public static void MainGo()
        {
            //excute in the context of an admin user
            ExecuteFor(UserClass.ClassAdmin);
            ExecuteFor(UserClass.ClassUser);
            Console.ReadLine();
        }
    }

Thread properties and methods

The Thread class has many methods and properties, and using the Systeam.Threading namespace makes control thread execution much simpler. So far we just created the thread and started it.

Here are two more members of the Thread class: the Sleep() method and the IsAlive property. Why does the thread sleep for a while, until the time is up before the terminal sleeps. To make the thread sleep directly, the Sleep method can be used. Observe the following code to create a threadSleep.cs file.

Static void WorkFunction()
        {
            String ThreadState;
            For (int i = 1; i <50000; i++ )
            {
                If (i % 5000 == 0 )
                {
                    ThreadState = Thread.CurrentThread.ToString();
                    Console.WriteLine("Worker"+ ThreadState);
                }
            }
            Console.WriteLine("Worker Function Complete" );
        }
        Public static void MainGo()
        {
            String ThreadState;
            Thread t = new Thread(new ThreadStart(WorkFunction));
            t.Start();
            While (t.IsAlive)
            {
                Console.WriteLine("Still waiting.Iam going back to sleep!!" );
                Thread.Sleep(1 );
            }
            ThreadState = t.ThreadState.ToString();
            Console.WriteLine("He's finally dene! Thread state is "+ ThreadState);
        }

First, a thread is created that directly creates a ThreadStart variable as a parameter. Use the IsAlice property to determine whether the thread is still executing. The rest of the code is not standard, but pay attention to other points. First, use sleep to make the thread sleep, and let other threads give up the execution time. The input parameter unit is millisecond.

 Thread priority

The priority of a thread determines the relative priority between threads. The ThreadPriority enumeration defines the value that can be used to set the thread priority. The available values ​​are:
● Highest (highest value)
● AboveNormal (higher than normal)

●Normal (normal value)

●BelowNormal (below the normal value)

      ●Lowest (lowest value)

When the runtime creates a thread but has not assigned a priority, the thread’s initial priority is Normal. However, you can change the priority by using the ThreadPriority enumeration. Before we introduce the example of thread priority, let’s discuss the thread’s priority. Create a simple thread instance that displays the name, status, and priority information of the current thread, thread_ priority.cs:

Public class ThreadPriority
    {
        Public static Thread worker;
        Public static void MainGo()
        {
            Console.WriteLine("Entering void Main()" );
            Worker = new Thread(new ThreadStart(FindPriority));
            worker.Name = "FindPriority() Thread" ;
            worker.Start();
            Console.WriteLine("Exiting void Main()" );
        }
        Public static void FindPriority()
        {
            Console.WriteLine("Name:"+worker.Name );
            Console.WriteLine("State:"+worker.ThreadState.ToString() );
            Console.WriteLine("Priority:"+worker.Priority.ToString() );
        }
    }

This code is very simple, defining the method FindPriority(), which shows the name, state, and priority status of the current thread. Worker threads are run at Normal priority. The following is the code to change the priority.

worker2.Priority = System.Threading.ThreadPriority.Highest;

It should be noted that the application cannot limit the operating system to modify the priority assigned by the developer to the thread, because the application controls all threads. They know how to arrange for you, old iron.

Timer and callback

Since threads do not run in the same order as the rest of the application code, we are unable to determine whether the thread’s actions affect a particular shared resource will be completed before another thread accesses the shared resource. So in order to solve these problems. Using a timer, you can execute a method at a specific time. Check if it is complete. This is a very simple model. But you can use a lot of situations.

      The timer consists of two objects: TimerCallback and Timer. The TimerCallback delegate defines methods that are executed at specified intervals, and the Timer object itself is a timer. TimerCallback will – a specific method associated with the timer. The Timer constructor (obtained by overload) requires 4 arguments. The first is the TimerCallback object specified earlier, and the second is an object that can be used to transfer the state to the specified method. The last two parameters are the time after the method is started, and the time interval after which the TimerCallback method is called. These parameters can be integer or long, representing the number of milliseconds. In the following content, the System.TimeSpan object is used, which can specify the interval in units of clock ticks, milliseconds, seconds, minutes, hours, or days.

Public class TimerExample
    {
        Private string message;//message
        Private static Timer tmr;//timer
        Private static bool complete; / / is completed
    }

The above definition is very simple, declare tmr as a static variable and apply to the entire class.

Public class TimerExample
    {
        Private string message;//message
        Private static Timer tmr;//timer
        Private static bool complete; / / is completed
        Public void GenerateText()
        {
            StringBuilder sb = new StringBuilder();
            For (int i=1;i<200;i++ )
            {
                sb.Append("This is Line"+i.ToString()+ System.Environment.NewLine);
            }
            Message = sb.ToString();
        }
        Public void GetText(object state)
        {
            If (message == null )
                Return ;
            Console.WriteLine("message is:"+ message);
            tmr.Dispose();
            Complete = true ;
        }
        Public void MainGo()
        {
            TimerExample obj = new TimerExample();
            Thread t = new Thread(new ThreadStart(GenerateText));
            t.Start();
            TimerCallback timerCallback = new TimerCallback(GetText);
            Tmr = new Timer(timerCallback,null,TimeSpan.Zero,TimeSpan.FromSeconds(2 ));
            Do
            {
                If (complete)
                    Break ;
            } while (true );
            Console.WriteLine("Exiting Main." );
            Console.ReadLine();
        }
    }

Triggered by the Timer timer once every two seconds. If the message has not been set, the method will exit, otherwise a message will be output. The timer is then removed by the GC.

Thread life cycle

When you schedule a thread, the thread goes through several states, including no startup, activation, and sleep state. The Thread class contains methods that allow startup, stop, resume, terminate, suspend, and wait for threads. Use the thread’s ThreadState to determine the current state of the thread. This state is an enumeration value, which is defined as follows.

[Flags]
    Public enum ThreadState
    {
        Running = 0 ,
        = 1 StopRequested ,
        SuspendRequested = 2 ,
        Background = 4 ,
= the Unstarted ,
= Stopped ,
        = 32 WaitSleepJoin The ,
        = 64 Suspended ,
        = 128 AbortRequested ,
        Aborted = 256 
    }

Thread method

Threads also operate four major operations, such as thread sleep, interrupt threads, suspend and resume threads, destroy threads, and connect threads. That Sleep() is not much. When the thread goes to sleep, he enters the WaitSleepJoin state. If the thread is in a sleep state, the method of waking up the thread before reaching the specified sleep time is only Interrupt(). This method will be relocated to the dispatch queue; create a ThreadInterupt.cs below. Here is the code:

Wake up thread

Public class ThreadSleppJoin
    {
        Public static Thread sleeper;
        Public static Thread worker;
        Public static void MainGo()
        {
            Console.WriteLine("Entering the void Main!" );

            Sleeper = new Thread(new ThreadStart(SleepingThread));Worker 
            = new Thread(new ThreadStart(AwakeTheThread));
            sleeper.Start();
            worker.Start();
        }
        Public static void SleepingThread()
        {
            For (int i = 1; i < 50; i++ )
            {
                Console.Write(i + " " );
                If (i == 10 || i == 20 || i == 30 )
                {
                    Console.WriteLine("Going to sleep at:" + i);
                    Thread.Sleep(20 );
                }
            }
        }
        Public static void AwakeTheThread()
        {
            For (int i = 50; i < 100; i++ )
            {
                Console.Write(i + " " );
                If (sleeper.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
                {
                    Console.WriteLine("Interrupting the slepping thread" );
                    sleeper.Interrupt();
                }
            }
        }
    }
 In the above example, when the counter reaches 10, 20, and 30, the first thread (sleeper thread) goes to sleep. The second thread (worker thread) checks if the first thread is in a sleep state. If so, the first thread is interrupted and placed back into the dispatch queue. Interrupt (method is the best way to reactivate a sleeping thread, if the process waiting for the resource ends and you want the thread to be active, you can use this feature.

Pause and resume threads

The Supend() and Resume() methods of the Thread class can be used to pause and resume threads. The Supend() method will close the thread indefinitely until another thread wakes it up. When the Resume() method is called, the thread will be in the SuspendRequested or Suspended state.

Public partial class Form1 : Form
    {
        Private Thread primeNumberThread;
        Public Form1()
        {
            InitializeComponent();
        }

        Private void button1_Click(object sender, EventArgs e)
        {
            primeNumberThread = new Thread(new ThreadStart(AddItemToListBox));
            primeNumberThread.Name = "Prime Numbers Example" ;
            primeNumberThread.Priority = ThreadPriority.BelowNormal;
            primeNumberThread.Start();
        }
        Long num = 0 ;
        Public void AddItemToListBox()
        {
            While (1==1 )
            {
                If (primeNumberThread.ThreadState != System.Threading.ThreadState.Suspended || primeNumberThread.ThreadState != System.Threading.ThreadState.SuspendRequested)
                {
                    Thread.Sleep(1000 );
                    Num += 1 ;
                    This .listBox1.Items.Add(num.ToString());
                }
                Else
                    Break ;
            }
        }

        Private void button2_Click(object sender, EventArgs e)
        {
            If (primeNumberThread.ThreadState== System.Threading.ThreadState.Running)
            {
                primeNumberThread.Suspend();
            }
        }

        Private void Form1_Load(object sender, EventArgs e)
        {
            This .Dispose();
        }

        Private void button3_Click(object sender, EventArgs e)
        {
            If (primeNumberThread.ThreadState != System.Threading.ThreadState.Suspended || primeNumberThread.ThreadState != System.Threading.ThreadState.SuspendRequested)
            {
                primeNumberThread.Resume();
            }
        }
    }

Microsoft Tip: Do not use the Suspend and Resume methods to synchronize thread activity. Is there a way to know what code to pause when executing a thread. If you hold a lock during a thread safety permission evaluation, the AppDomain in other threads may be blocked. If you suspend a thread-executing class constructor , the attempt to use the class in the AppDomain in other threads is blocked. Deadlocks can easily occur.

 Destroy thread

      The Abort() method can be used to destroy the current thread. If for some reason (such as a thread executing too long or the user chooses to cancel) to terminate the thread, you can use the Abort0 method. For example, if the search process has been running for a long time, you can terminate the process. The search can continue, but the user has already got the desired result and no longer has to continue executing the thread on the search routine. A ThreadAbortException is thrown when the Abort() method is called on a thread. If the exception is not caught in the thread’s code, the thread will terminate. Care should be taken before writing general exception handling code in a method accessed in a multithreaded environment. Because catch(Exception e) also catches a ThreadAbortException (may not want to recover from it). Thus, the ThreadAbortException is not easy to stop, and the program flow will not continue as expected.
Private void button4_Click(object sender, EventArgs e)
        {
            primeNumberThread.Abort();
        }

Such a thread is destroyed. It doesn’t exist anymore. If you want to use it, you need to create an instance object.

Connection thread

      The Join() method will pause the given thread until the current thread terminates. When the Join() method is called on a given thread instance, the thread will be placed in the WaitsleepJoin state. If a thread depends on another thread, you can use this method. Connecting two threads means that when you call Join (the method, the running thread will enter the WaitsleepJoin state, and until Join is called (the method’s thread completes the task, the thread will return to the Running state. This sounds a bit confusing, The following uses an example thread joining.cs to illustrate the code as follows:
Public class Threadjoining
    {
        Public static Thread SecondThread;
        Public static Thread FirstThread;
        Static void First()
        {
            For (int i=1;i<=50;i++ )
            {
                Console.Write(i+" " );
            }
        }
        Static void Second()
        {
            FirstThread.Join();
            For(int i=51;i<=100;i++ )
            {
                Console.Write(i+" " );
            }
        }
        Public static void MainGo()
        {
            FirstThread = new Thread(new ThreadStart(First));
            SecondThread = new Thread(new ThreadStart(Second));
            FirstThread.Start();
            SecondThread.Start();
        }
    }

The purpose of this simple example is to sequentially output the numbers to the console, starting from 1 to 50. The First0 method will output the first 50 digits, and the Second method will output the number from 51 to 100. If there is no FirstThread.Join execution flow in the Second() method, it will be switched back between the two methods, and the output will be confusing (try to comment out the line and run the example again). By calling the FirstThread.Join() method in the Second() method, the execution of the Second0 method is suspended until the code in the FirstThread (First0 method) is executed.

he Join() method is overloaded; its only – argument can be an integer or a TimeSpan, which returns a Boolean value. After calling an overloaded version of this method, the thread will pause until another thread finishes or times out (whichever occurs first). If the thread has finished, the return value is True, otherwise it will be False.

Thread is not omnipotent

      Multithreaded applications require a lot of resources. Threads require memory to store thread-local memory, and the number of threads used is limited by the total amount of memory available. Currently, memory is quite cheap, so many computers have a lot of memory. However, not all computers are like this. If you run the application on an unknown hardware configuration, you can’t assume that the application has enough memory, and you can’t assume that only one stalk will generate threads and consume system resources. A computer has a lot of memory space, and does not mean that all memory is used by an application. 

      Each thread also causes additional processor overhead. If you create too many threads in your application, you limit the total execution time of the thread. Therefore, it takes more time for the processor to switch between threads than the instructions contained in the execution thread. If the application creates more threads, the application will get more execution time than other processes with fewer threads.