NET defines multi-threading functionality in the System.Threading namespace. Therefore, to use multithreading, you must first declare a reference to this namespace (using System.Threading;).
a. Start a thread. As the name suggests, "start a thread" means to create and start a thread. The following code can achieve this:
Thread thread1 = new Thread(new ThreadStart(Count));
Where Count is the function to be executed by the new thread.
b. Kill the thread
"Killing a thread" is to eradicate a thread. In order not to waste your efforts, it is best to determine whether it is still alive (through the IsAlive attribute) before killing a thread, and then call the Abort method to kill the thread.
c. Pausing the thread means letting a running thread sleep for a period of time. For example, thread.Sleep(1000); is to let the thread sleep for 1 second.
d. Priority needs no explanation. The hreadPriority attribute in the Thread class is used to set the priority, but there is no guarantee that the operating system will accept the priority. The priority of a thread can be divided into 5 types: Normal, AboveNormal, BelowNormal, Highest, and Lowest. Specific implementation examples are as follows:
thread.Priority = ThreadPriority.Highest;
e. Suspend thread
The Suspend method of the Thread class is used to suspend the thread until Resume is called before the thread can continue to execute. If the thread is already suspended, that won't work.
if (thread.ThreadState = ThreadState.Running)
{
thread.Suspend();
}
f. The recovery thread is used to resume the suspended thread so that it can continue to execute. If the thread is not suspended, it will not work.
if (thread.ThreadState = ThreadState.Suspended)
{
thread.Resume();
}
An example is listed below to illustrate simple threading functionality. This example comes from the help documentation.
using System;
using System.Threading;
// Simple threading scenario: Start a static method running
// on a second thread.
public class ThreadExample {
// The ThreadProc method is called when the thread starts.
// It loops ten times, writing to the console and yielding
// the rest of its time slice each time, and then ends.
public static void ThreadProc() {
for (int i = 0; i < 10; i++) {
Console.WriteLine("ThreadProc: {0}", i);
// Yield the rest of the time slice.
Thread.Sleep(0);
}
}
public static void Main() {
Console.WriteLine("Main thread: Start a second thread.");
// The constructor for the Thread class requires a ThreadStart
// delegate that represents the method to be executed on the
// thread. C# simplifies the creation of this delegate.
Thread t = new Thread(new ThreadStart(ThreadProc));
// Start ThreadProc. On a uniprocessor, the thread does not get
// any processor time until the main thread yields. Uncomment
// the Thread.Sleep that follows t.Start() to see the difference.
t.Start();
//Thread.Sleep(0);
for (int i = 0; i < 4; i++) {
Console.WriteLine("Main thread: Do some work.");
Thread.Sleep(0);
}
Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.");
t.Join();
Console.WriteLine("Main thread: ThreadProc.Join has returned. Press Enter to end program.");
Console.ReadLine();
}
}
This code produces output similar to the following:
Main thread: Start a second thread.
Main thread: Do some work.
ThreadProc: 0
Main thread: Do some work.
ThreadProc: 1
Main thread: Do some work.
ThreadProc: 2
Main thread: Do some work.
ThreadProc: 3
Main thread: Call Join(), to wait until ThreadProc ends.
ThreadProc: 4
ThreadProc: 5
ThreadProc: 6
ThreadProc: 7
ThreadProc: 8
ThreadProc: 9
Main thread: ThreadProc.Join has returned. Press Enter to end program.
In Visul C#, the System.Threading namespace provides some classes and interfaces that enable multi-threaded programming. There are three methods for creating threads: Thread, ThreadPool, and Timer. Here is a brief introduction to how to use them one by one.
1. Thread
This is perhaps the most complex method, but it provides a variety of flexible control over threads. First, you must use its constructor to create a thread instance. Its parameters are relatively simple, with only one ThreadStart delegate: public Thread(ThreadStart start); then call Start() to start it. Of course, you can use its Priority attribute to set or Get its running priority (enum ThreadPriority: Normal, Lowest, Highest, BelowNormal, AboveNormal).
The following example first generates two thread instances t1 and t2, then sets their priorities respectively, and then starts the two threads (the two threads are basically the same, except that their output is different, t1 is "1" and t2 is "2", According to the ratio of the number of characters they output, we can roughly see the ratio of CPU time they occupy, which also reflects their respective priorities).
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(Thread1));
Thread t2 = new Thread(new ThreadStart(Thread2));
t1.Priority = ThreadPriority.BelowNormal;
t2.Priority = ThreadPriority.Lowest;
t1.Start();
t2.Start();
}
public static void Thread1()
{
for (int i = 1; i < 1000; i++)
{//Write a "1" each time a loop is run.
dosth();
Console.Write("1");
}
}
public static void Thread2()
{
for (int i = 0; i < 1000; i++)
{//Write a "2" each time a loop is run.
dosth();
Console.Write("2");
}
}
public static void dosth()
{//Used to simulate complex operations
for (int j = 0; j < 10000000; j++)
{
int a=15;
a = a*a*a*a;
}
}
The result of running the above program is:
111111111111111111111111111111111111111111211111111111111111111111111111111111111111112
111111111111111111111111111111111111111111211111111111111111111111111111111111111111112
111111111111111111111111111111111111111111211111111111111111111111111111111111111111112
From the above results, we can see that the t1 thread takes up much more CPU time than t2. This is because the priority of t1 is higher than that of t2. If we set the priorities of both t1 and t2 to Normal, the results are as follows picture:
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
From the above example, we can see that its structure is similar to the win32 worker thread, but it is simpler. You only need to use the function to be called by the thread as a delegate, and then use the delegate as a parameter to construct a thread instance. When Start() is called to start, the corresponding function will be called, and execution will begin from the first line of that function.
Next, we combine the ThreadState property of the thread to understand the control of the thread. ThreadState is an enumeration type that reflects the state of the thread. When a Thread instance is first created, its ThreadState is Unstarted; when the thread is started by calling Start(), its ThreadState is Running; after the thread is started, if you want it to pause (block), you can call Thread. Sleep() method, it has two overloaded methods (Sleep(int), Sleep(Timespan)), which are just different formats for representing the amount of time. When this function is called in a thread, it means that the thread will Block for a period of time (the time is determined by the number of milliseconds or Timespan passed to Sleep, but if the parameter is 0, it means suspending this thread to enable other threads to execute, specifying Infinite to block the thread indefinitely), at this time its ThreadState Will become WaitSleepJoin. Another thing worth noting is that the Sleep() function is defined as static? ! This also means that it cannot be used in conjunction with a thread instance, that is, there is no call similar to t1.Sleep(10)! Just like this, the Sleep() function can only be called by the thread that needs to "Sleep" itself, and is not allowed to be called by other threads. Just as when to Sleep is a personal matter that cannot be decided by others. But when a thread is in the WaitSleepJoin state and has to wake it up, you can use the Thread.Interrupt method, which will throw a ThreadInterruptedException on the thread. Let's look at an example first (note the calling method of Sleep):
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(Thread1));
t1.Start();
t1.Interrupt();
E.WaitOne();
t1.Interrupt();
t1.Join();
Console.WriteLine(“t1 is end”);
}
static AutoResetEvent E = new AutoResetEvent(false);
public static void Thread1()
{
try
{//It can be seen from the parameters that it will cause hibernation
Thread.Sleep(Timeout.Infinite);
}
catch(System.Threading.ThreadInterruptedException e)
{//Interrupt handler
Console.WriteLine (" 1st interrupt");
}
E.Set();
try
{// sleep
Thread.Sleep(Timeout.Infinite);
}
catch(System.Threading.ThreadInterruptedException e)
{
Console.WriteLine (" 2nd interrupt");
}//Pause for 10 seconds
Thread.Sleep (10000);
}
The running result is: 1st interrupt
2nd interrupt
(After 10s)t1 is end
From the above example, we can see that the Thread.Interrupt method can wake up the program from a blocking (WaitSleepJoin) state and enter the corresponding interrupt handler, and then continue execution (its ThreadState also changes to Running). The use of this function must Note the following points:
1. This method can not only wake up the blocking caused by Sleep, but also is effective for all methods that can cause the thread to enter the WaitSleepJoin state (such as Wait and Join). As shown in the above example, when using it, you should put the method that causes thread blocking into the try block, and put the corresponding interrupt handler into the catch block.
2. Call Interrupt on a certain thread. If it is in the WaitSleepJoin state, it will enter the corresponding interrupt handler for execution. If it is not in the WaitSleepJoin state at this time, it will be interrupted immediately when it enters this state later. If Interrupt is called several times before interrupting, only the first call is valid. This is why I used synchronization in the above example, so as to ensure that the second call to Interrupt is called after the first interrupt, otherwise it may cause the second The call has no effect (if it is called before the first interrupt). You can try removing synchronization. The result is likely to be: 1st interrupt
The above example also uses two other methods to make the thread enter the WaitSleepJoin state: using the synchronization object and the Thread.Join method. The use of the Join method is relatively simple. It means that the current thread calling this method blocks until the other thread (t1 in this example) terminates or the specified time passes (if it also takes a time parameter). When two If any condition (if any) occurs, it immediately ends the WaitSleepJoin state and enters the Running state (the condition can be determined based on the return value of the .Join method. If it is true, the thread is terminated; if it is false, the time is up). The thread can also be suspended using the Thread.Suspend method. When a thread is in the Running state and the Suspend method is called on it, it will enter the SuspendRequested state, but it will not be suspended immediately until the thread reaches a safe point. The thread hangs, at which point it will enter the Suspended state. If called on a thread that is already Suspended, it will be invalid. To resume operation, just call Thread.Resume.
The last thing we talk about is the destruction of threads. We can call the Abort method on the thread that needs to be destroyed, and it will throw a ThreadAbortException on this thread. We can put some code in the thread into the try block, and put the corresponding processing code into the corresponding catch block. When the thread is executing the code in the try block, if Abort is called, it will jump into the corresponding catch block. Executed within the catch block, it will terminate after executing the code in the catch block (if ResetAbort is executed within the catch block, it will be different: it will cancel the current Abort request and continue to execute downwards. Therefore, if you want to ensure that a thread terminates, it is best to use Join , as in the above example).
2. ThreadPool
Thread Pool (ThreadPool) is a relatively simple method. It is suitable for short tasks that require multiple threads (such as some threads that are often blocked). Its disadvantage is that it cannot control the created threads. Nor can its priority be set. Since each process has only one thread pool, and of course each application domain has only one thread pool (line), you will find that the member functions of the ThreadPool class are all static! When you call ThreadPool.QueueUserWorkItem, ThreadPool.RegisterWaitForSingleObject, etc. for the first time, a thread pool instance will be created. The following is an introduction to the two functions in the thread pool:
public static bool QueueUserWorkItem( //Returns true if the call is successful
WaitCallback callBack,//The delegate called by the thread to be created
object state //Parameters passed to the delegate
)//Its another overloaded function is similar, except that the delegate takes no parameters. The function of this function is to queue the thread to be created to the thread pool. When the number of available threads in the thread pool is not zero (the thread pool has created threads If the number is limited (the default value is 25), this thread will be created. Otherwise, it will be queued to the thread pool and wait until it has available threads.
public static RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject,//WaitHandle to be registered
WaitOrTimerCallback callBack, // Thread call delegate
object state,//Parameters passed to the delegate
int TimeOut,//Timeout, unit is milliseconds,
bool executeOnlyOnce file:// Whether to execute only once
);
public delegate void WaitOrTimerCallback(
object state,//that is, the parameters passed to the delegate
bool timedOut//true means due to timeout call, otherwise it means waitObject
);
The function of this function is to create a waiting thread. Once this function is called, this thread will be created. It will be in a "blocked" state until the parameter waitObject changes to the terminated state or the set time TimeOut arrives. It is worth noting that this "Blocking" is very different from the WaitSleepJoin state of Thread: when a Thread is in the WaitSleepJoin state, the CPU will wake it up regularly to poll and update the status information, and then enter the WaitSleepJoin state again. Thread switching is very resource-intensive; and The thread created with this function is different. The CPU will not switch to this thread before it is triggered to run. It neither takes up CPU time nor wastes thread switching time, but how does the CPU know when to run it? In fact, the thread pool will generate some auxiliary threads to monitor these trigger conditions. Once the conditions are met, the corresponding threads will be started. Of course, these auxiliary threads themselves also take up time, but if you need to create more waiting threads, use the thread pool's The advantages become more obvious. See example below:
static AutoResetEvent ev=new AutoResetEvent(false);
public static int Main(string[] args)
{ ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitThreadFunc),
4,
2000,
false//Indicates that the timer will be reset every time the waiting operation is completed until logging out and waiting.
);
ThreadPool.QueueUserWorkItem (new WaitCallback (ThreadFunc),8);
Thread.Sleep (10000);
return 0;
}
public static void ThreadFunc(object b)
{ Console.WriteLine ("the object is {0}",b);
for(int i=0;i<2;i++)
{ Thread.Sleep (1000);
ev.Set();
}
}
public static void WaitThreadFunc(object b,bool t)
{ Console.WriteLine ("the object is {0},t is {1}",b,t);
}
The result of its operation is:
the object is 8
the object is 4,t is False
the object is 4,t is False
the object is 4,t is True
the object is 4,t is True
the object is 4,t is True
From the above results, we can see that thread ThreadFunc ran once, and WaitThreadFunc ran 5 times. We can judge the reason for starting this thread from the bool t parameter in WaitOrTimerCallback: if t is false, it means waitObject, otherwise it is due to timeout. In addition, we can also pass some parameters to the thread through object b.
3. Timer
It is suitable for methods that need to be called periodically. It does not run in the thread that created the timer. It runs in a separate thread automatically allocated by the system. This is similar to the SetTimer method in Win32. Its structure is:
public Timer(
TimerCallback callback,//Method to be called
object state,//parameters passed to callback
int dueTime,//How long will it take to start calling callback?
int period//The time interval for calling this method
); // If dueTime is 0, callback executes its first call immediately. If dueTime is Infinite, the callback does not call its method. The timer is disabled, but it can be re-enabled using the Change method. If period is 0 or Infinite, and dueTime is not Infinite, callback calls its method once. The timer's periodic behavior is disabled, but it can be re-enabled using the Change method. If period is zero (0) or Infinite, and dueTime is not Infinite, the callback calls its method once. The timer's periodic behavior is disabled, but it can be re-enabled using the Change method.
If we want to change the period and dueTime of the timer after creating it, we can change it by calling the Change method of Timer:
public bool Change(
int dueTime,
int period
);//Obviously the two parameters changed correspond to the two parameters in Timer
public static int Main(string[] args)
{
Console.WriteLine ("period is 1000");
Timer tm=new Timer (new TimerCallback (TimerCall),3,1000,1000);
Thread.Sleep (2000);
Console.WriteLine ("period is 500");
tm.Change (0,800);
Thread.Sleep (3000);
return 0;
}
public static void TimerCall(object b)
{
Console.WriteLine ("timercallback; b is {0}",b);
}
The result of its operation is:
period is 1000
timercallback;b is 3
timercallback;b is 3
period is 500
timercallback;b is 3
timercallback;b is 3
timercallback;b is 3
timercallback;b is 3
Summarize
From the above brief introduction, we can see the occasions where they are used respectively: Thread is suitable for occasions that require complex control of threads; ThreadPool is suitable for short tasks that require multiple threads (such as some that are often blocked). Thread); Timer is suitable for methods that need to be called periodically. As long as we understand their usage characteristics, we can choose the appropriate method well.