You should know all about multithreading in .NET
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();
}
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();
}
}
}
}
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
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
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.
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.
Comment disabled