C # multithreaded programming (1) -- threads, thread pools, and tasks,
A new multi-threaded programming series is introduced, which mainly describes multi-threaded programming in C. Multithreading can be used for two purposes: one is to prevent the UI thread from being occupied by time-consuming programs, resulting in interface freezing; the other is to use resources of multi-core CPU to improve operation efficiency.
I did not give a very in-depth explanation, but mainly focused on practical use. My main series is the summary of CLR via C #. Jeffrey Richter, author of this book, is a consultant for C #. He has a deep insight into windows. Especially in the multi-thread section, the book provides a very thorough explanation. If you cannot explain it in this article or want to learn more deeply, you can find "CLR via C #" to study it carefully.
When a program that will run for a long time, such as obtaining data from the server, the client remains in the waiting state during the execution of the program, waiting for the program to be completed, and then executing other code. If it is a UI program, the user will feel that the interface is stuck, affecting the user experience. We hope that the choppy program can "secretly" run in the background without affecting the interface. To solve this problem, multithreading is required. Some threads are responsible for responding to interface operations, while others are responsible for background computing. The Code is as follows:
Public void GetData ()
{Var thread = new Thread () => LoadDataFromServer (); thread. start ();}
Public void LoadDataFromServer (){
// Read analog data
Thread. Sleep (2000 );
Console. WriteLine ("read complete. ");
}
Thread is the thread you created, and then call the Start () method, the thread will Start to execute, LoadDataFromServer () is the method you want to execute, here is to read data from the service, windows will schedule this thread and decide when it will start to be executed. In this way, the new thread is responsible for reading data, the main thread does not wait, continue to execute, and the interface does not get stuck. This is good because the interface is smooth and asynchronous, but this is not the optimal solution. When the program runs for a long time and reads data from the server every time, a new thread is required to avoid interface freezing. After the data is loaded, the new thread is useless. Creating a thread has a high overhead (I will not introduce the specific overhead. If you are interested, you can query relevant information online. The Clr via C # section describes the overhead in detail ), if every created thread is not released after running, it is saved and used once, can it save resources? The thread pool does this, for example:
// Some operations: ThreadPool. QueueUserWorkItem () => LoadDataFromServer (); // other operations
As you can see, the previous Code does not explicitly create a thread. Instead, it places the method in the ThreadPool. QueueUserWorkItem () method. ThreadPool is responsible for creating and managing threads. At the beginning of the program, ThreadPool is called for the first time. When a thread in the thread pool does not exist, the thread pool creates a new thread. When the program calls the thread pool again, if there are Idle threads in the thread pool, the idle thread execution program is called directly. If the program calls the thread pool, there are no Idle threads in the thread pool and the CPU is "not saturated, the thread pool creates a new thread. In fact, when a thread pool is called, it is equivalent to "hanging" the method to be executed on the task queue of the thread pool. When the CPU is in the "not saturated" status, the thread pool calls a thread to execute tasks in the thread pool task queue.
The ThreadPool. QueueUserWorkItem () method has a problem, that is, there is no convenient way to obtain the return value of the method, and it is unknown when the LoadDataFromServer () method is executed. To solve this problem, C # introduces tasks and generic tasks <T>. The Code is as follows:
var data = Task.Run(() => LoadDataFromServer()).Result;
First, let's explain the Task. run () is for ThreadPool. the encapsulation of the QueueUserWorkItem () method. This method returns the Task and can then call the task. result to obtain the returned value of LoadDataFromServer. In fact, this code will not be executed asynchronously because the thread where data is located will wait for the returned value of LoadDataFromServer (). Otherwise, data will have no value and the program will not be able to execute, so the thread is blocked at this time, this thread will continue execution only after the task is completed. To solve this problem, C # introduces the async and await keywords. The Code is as follows:
Public async void LoadData () {var data = await Task. Run () => LoadDataFromServer ());
Console. WriteLine (data );}
Public string LoadDataFromServer (){
// Simulate reading data from the server
Thread. Sleep (2000 );
Return "Data ";
}
C # specifies that the await keyword can only be used in the method marked with async. This keyword will compile the code after await into a state machine. After the LoadDataFromServer () method is executed, the program re-enters the LoadData () method and continues execution from await. This keyword does not block the thread (how does the compiler compile the Asynchronous Method of await into a state machine, CLR via C # is described in detail in section 28.4 ).
The above is the first part of multi-threaded programming-Thread, ThreadPool and Task. The next section will continue to explain other features and methods of the Task.