Rough explanation of .net threads

problem found

After you order takeout, will you do nothing else and wait for the takeout to arrive? Of course not!

Let’s take a look at the world of code:

public void Query(){
    
    
    // 当前线程 向 数据库服务器 发起查询命令
    // 在 数据库服务器 返回数据之前,当前线程 一直等待,不干活了!!!
    var data = Database.Query(); 
}

Assume in a request response:

线程用 5ms 来验证用户的输入的参数;
线程用 50ms 来等待数据库返回;
线程用 5ms 序列化数据响应返回给用户;
可以看到在 60ms 中,线程摸鱼 50ms。

Many web frameworks will create a thread to process a request.
If 100 users request this method within a moment, then 100 threads will have to be arranged.
Is there a way to make the first thread wait for data to be returned? When, first go to receive the N+1th user (verify request parameters and so on),
this can greatly reduce the number of threads~

Through the above examples, I believe you have understood: asynchronous is to avoid letting threads fish.

concepts and theories

Next, in order to communicate and prompt more effectively, we will still use professional terminology.

Review thread blocking, sleeping, and suspending. The main thing is to understand the definition of blocking and when blocking will occur.

Thread blocking:
Thread t = new Thread(()=>{
    
    
    // 阻塞:线程 被动 地等待外部返回,才能继续执行
    var resp = Http.Get(url); // 需要等待网络传输文档
});
Thread sleep:
Thread t = new Thread(()=>{
    
    
    // 睡眠:线程 主动 停止执行片刻,然后继续执行
    Thread.Sleep(1000);
});
Thread hangs:
// 伪代码,C# 的 ThreadPool 没有这些方法

// 主动叫线程去休息
ThreadPool.Recycle(t)

// 等到有工作了,再叫线程处理执行
t = ThreadPool.GetThread();
t.Run(fun);
Synchronous:

I give a relatively easy-to-understand definition of synchronization: steps in sequence, and only do one thing at a time.
I saw the word synchronization before, and mistakenly thought it meant doing several things at the same time. Wrong! ! !

// The thread will execute the following code step by step. This process is called synchronization.

// 先发完短信
SMS.Send(msg); // 2秒

// 再发邮件
Email.Send(smg); // 1秒

// 总耗时 3秒
Parallel:

Refers to two or more events (or threads) occurring at the same time.

// 分别创建两个线程并行去执行,谁也不用等待谁~
Thread t1 = new Thread(()=>{
    
    
    SMS.Send(msg); // 2秒
});

// t2 线程不需要等待 t1 线程
Thread t2 = new Thread(()=>{
    
    
    Email.Send(smg); // 1秒
});

// 总耗时 2秒

Microsoft official documentation-Asynchronous programming using Async and Await

Example of how Microsoft makes breakfast:

倒一杯咖啡。
加热平底锅,然后煎两个鸡蛋。
煎三片培根。
烤两片面包。
在烤面包上加黄油和果酱。
倒一杯橙汁。

Insert image description here

  • Synchronization requires a single person (single thread) to do it step by step from 1 to 6 - low efficiency.
  • Parallelism involves multiple people (multi-threading), one pouring coffee; one frying eggs; one... doing it at the same time - high efficiency but high labor cost.
  • Asynchronous is a single person (single thread), lighting the fire to heat the pan, the pan has to wait for it to get hot, then put the bread into the toaster first...
Asynchronous:
指的是,当线程遇到阻塞时,让线程先去执行其它工作~

We should have experienced that when a person has to switch back and forth on many things, it is easy to make mistakes.

To make breakfast, we light the fire and heat the pan and then go to make toast, but when is the pan ready, when do we switch back to frying eggs, or when do we pour orange juice.

要将代码的执行过程写成异步的,也不是容易的事情。

Fortunately, C# provides the two keywords async and await, making it easy to create asynchronous methods (almost as easy as creating synchronous methods) - Original words from Microsoft official documentation

After the theoretical explanation, it’s time to practice~

async modifier

public void Get()
{
    
    
    // 这是一个 同步方法
    // 如果这个内部有会发生阻塞的功能代码,比如读取网络资源,
    // 那么一个线程运行这个方法遇到阻塞,这个线程就会摸鱼~
}

To declare a synchronous method as an asynchronous method, you first need to mark it with the async modifier.

public async void Get()
{
    
    
    // 这是一个 异步方法
    // 如果这个内部有会发生阻塞的功能代码
    // 那么一个线程运行这个方法遇到阻塞时,这个线程就会去做其它事情~
}
public async void Get()
{
    
    
    HttpClient httpClient = new HttpClient();
    httpClient.GetAsync("https://learn.microsoft.com/zh-cn/docs/");
}

After adding some code we need to observe, we get:

public static void Main()
{
    
    
    Console.WriteLine($"Main 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    Get();

    Console.WriteLine($"Main 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");


    Console.ReadKey();
}

// There is a problem with this code. I did it intentionally to compare it with the more complete code that will follow~

public static async void Get()
{
    
    
    Console.WriteLine($"Get 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    HttpClient httpClient = new HttpClient();
    httpClient.GetAsync("https://learn.microsoft.com/zh-cn/docs/");

    Console.WriteLine($"Get 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");
}

Console output after running:

Main 开始执行前线程 Id:1
Get  开始执行前线程 Id:1
Get  执行结束后线程 Id:1
Main 执行结束后线程 Id:1

Notice! ! ! Although the method is declared asynchronous at this time, the execution process is still synchronous! ! ! !

await operator

In Microsoft official documentation: async (C# reference):

An asynchronous method runs synchronously until its first await expression is reached, at which point the method is suspended until the awaited task completes.

If the method modified by the async keyword does not contain an await expression or statement, the method will be executed synchronously. A compiler warning will inform you of any asynchronous method that does not contain an await statement, as this situation may indicate an error. See Compiler Warning (Level 1) CS4014.

So the perfect code should look like this:

public static void Main()
{
    
    
    Console.WriteLine($"Main 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    Get(); // Get 方法虽然是声明为异步的,但依旧时同步执行

    Console.WriteLine($"Main 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");


    Console.ReadKey();
}

public static async void Get()
{
    
    
    Console.WriteLine($"Get 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    HttpClient httpClient = new HttpClient();

    // 加上 await 运算符,才是真正的异步执行!!!
    await httpClient.GetAsync("https://learn.microsoft.com/zh-cn/docs/");

    Console.WriteLine($"Get 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");
}

Console output after running:

Main 开始执行前线程 Id:1 # 线程1,进入 main 函数 
Get  开始执行前线程 Id:1 # 线程1,执行 Get  函数,遇到阻塞,但线程1被要求不能摸鱼,
Main 执行结束后线程 Id:1 # 于是看看有没有其它工作做,发现需要打印...
Get  执行结束后线程 Id:9 # 阻塞结束后,谁来执行剩下的代码呢?
               # 如果线程1有空,可以回来执行,如果线程1忙,则有其它线程接管
               # 由调度分配决定
我们自己定义的异步方法 Get() 和调用异步方法 httpClient.GetAsync,
只有 httpClient.GetAsync 是异步执行的。
也就是说单单使用 async 还不够,还得必须同时使用 await
Task class

Generally speaking, when we use httpClient.GetAsync, we hope to process the returned data.

Microsoft official documentation: Return types of asynchronous methods

  • Task represents a single operation that does not return a value and is usually performed asynchronously.
  • Task represents a single operation that returns a value and usually executes asynchronously.
  • The use of async void methods is generally discouraged for code other than event handlers because callers cannot await those methods and must implement different mechanisms to report successful completion or error conditions.
public static async void Get()
{
    
    
    const string url = "https://learn.microsoft.com/zh-cn/docs/";

    Console.WriteLine($"Get 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    HttpClient httpClient = new HttpClient();
    // 用 Task 来 = 一个异步操作
    Task<HttpResponseMessage> taskResp = httpClient.GetAsync(url);

    HttpResponseMessage resp = await taskResp;// 等待异步操作完成返回
    // 可以对 resp 进行一些处理
    
    Console.WriteLine($"Get 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");
}

The above code can be simplified to:

public static async void Get()
{
    
    
    const string url = "https://learn.microsoft.com/zh-cn/docs/";

    Console.WriteLine($"Get 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    HttpClient httpClient = new HttpClient();

    HttpResponseMessage resp = await httpClient.GetAsync(url);

    Console.WriteLine($"Get 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");
}

Example of multiple Tasks:

public static async void Get()
{
    
    
    Console.WriteLine($"Get 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    HttpClient httpClient = new HttpClient();

    var t1 = httpClient.GetAsync("https://learn.microsoft.com/");
    var t2 = httpClient.GetAsync("https://cn.bing.com/");
    var t3 = httpClient.GetAsync("https://www.cnblogs.com/");

    Console.WriteLine($"Get await 之前的线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");
    
    await Task.WhenAll(t1, t2, t3); // 等待多个异步任务完成

    //Task.WaitAll(t1, t2, t3);
    //await Task.Yield();
    //await Task.Delay(0);

    Console.WriteLine($"Get 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");
}

Console output after running:

Main 开始执行前线程 Id:1
Get  开始执行前线程 Id:1
Get  await 之前的线程 Id:1
Main 执行结束后线程 Id:1
Get  执行结束后线程 Id:14

According to the final version of the recommendations and specifications of Microsoft official documents:

public static void Main()
{
    
    
    Console.WriteLine($"Main 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    GetAsync().Wait();

    Console.WriteLine($"Main 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");


    Console.ReadKey();
}

// The use of async void methods is generally discouraged
// The asynchronous method name convention ends with Async

public static async Task GetAsync()
{
    
    
    Console.WriteLine($"Get 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    HttpClient httpClient = new HttpClient();

    var t1 = httpClient.GetAsync("https://learn.microsoft.com/");
    var t2 = httpClient.GetAsync("https://cn.bing.com/");
    var t3 = httpClient.GetAsync("https://www.cnblogs.com/");

    Console.WriteLine($"Get await 之前的线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");
    Task.WaitAll(t1, t2, t3); // 等待多个异步任务完成

    await Task.Yield();
    //await Task.Delay(0);

    Console.WriteLine($"Get 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

}

Console output after running:

Main 开始执行前线程 Id:1
Get  开始执行前线程 Id:1
Get  await 之前的线程 Id:1
Get  执行结束后线程 Id:5
Main 执行结束后线程 Id:1

test

public static async Task GetAsync()
{
    
    
    Console.WriteLine($"Get 开始执行前线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    Stopwatch sw = new Stopwatch();
    sw.Start();

    TestHttp(); // http 网络不稳定,不好观察时间,可以试试 TestIdle()

    sw.Stop();
    Console.WriteLine($"一共耗时:{
      
      sw.ElapsedMilliseconds} 毫秒");

    Console.WriteLine($"Get 执行结束后线程 Id:{
      
      Thread.CurrentThread.ManagedThreadId}");

    await Task.Yield();
}

public static void TestHttp()
{
    
    
    HttpClient httpClient = new HttpClient();

    List<Task<HttpResponseMessage>> tasks = new List<Task<HttpResponseMessage>>();
    for (int i = 0; i < 10; i++)
    {
    
    
        var t = httpClient.GetAsync("https://learn.microsoft.com/");
        tasks.Add(t);
    }

    Task.WaitAll(tasks.ToArray());

    foreach (var item in tasks)
    {
    
    
        var html = item.Result.Content.ReadAsStringAsync().Result;
    }
}

public static void TestIdle()
{
    
    
    List<Task> tasks = new List<Task>();
    for (int i = 0; i < 10; i++)
    {
    
    
        var t = Idle();
        tasks.Add(t);
    }

    Task.WaitAll(tasks.ToArray());
}

public static async Task Idle()
{
    
    
    // 可以用于模拟阻塞效果
    await Task.Delay(1000);

    // 不能用 Sleep 来模拟阻塞,Sleep 不是阻塞,是睡眠
    // Thread.Sleep(1000);
}

Copy
Main Thread ID before starting execution: 1
Get Thread ID before starting execution: 1
Total time spent: 604 milliseconds # 1 thread did the work of 10 threads, the time is about the same, happy~
Get Thread ID after execution: 1
Main Thread ID after execution: 1
At this point, the three knowledge points of asynchronous programming in C#, async, await, and Task, have been explained.

In the process of writing examples,
I found that many methods of the HttpClient class are asynchronous methods.
I vaguely remember that there were synchronous methods and asynchronous methods to choose from before.
It seems that Microsoft is forcing everyone to make progress~

Guess you like

Origin blog.csdn.net/kalvin_y_liu/article/details/128964175