zoukankan      html  css  js  c++  java
  • C#中关于Task.Yeild()的探究

          在与同事讨论async/await内部实现的时候,突然想到Task.Yeild()这个函数,为什么呢,了解一点C#async/await内部机制的都知道,在await一个异步任务(函数)的时候,它会先判断该Task是否已经完成,如果已经完成,则继续执行下去,不会返回到调用方,原因是尽量避免线程切换,因为await后面部分的代码很可能是另一个不同的线程执行,而Task.Yeild()则可以强制回到调用方,或者说主动让出执行权,给其他Task执行的机会,可以把Task理解为协程,Task.Yeild()和Thread.sleep(0)有点相同。

          为了证明我的结论成立,请看代码:

     1 public static async Task Test1()
     2 {
     3      await Task.CompletedTask;
     4      Thread.Sleep(1000);
     5      Console.WriteLine("Test1任务完成");
     6 }
     7 public static async Task Test2()
     8 {
     9      await Task.Delay(1);
    10      Thread.Sleep(1000);
    11      Console.WriteLine("Test2任务完成");
    12 }
    13 public static async Task Test3()
    14 {
    15      await Task.Yield();
    16      Thread.Sleep(1000);
    17      Console.WriteLine("Test3任务完成");
    18 }
    19 static void Main(string[] args)
    20 {
    21      Console.WriteLine(DateTime.Now);
    22      _ = Test1();
    23      Console.WriteLine(DateTime.Now);
    24      Console.ReadLine();
    25 }

          按照开头的理论,Test1()异步函数由于await了一个已经完成的任务,所以会继续往下执行,阻塞1秒钟,然后回到调用方,打印的时间之差会相隔一秒。

    image_thumb1_thumb

          Test2()异步函数由于await了一个未完成的任务(1ms对于CPU来说是很长的了),所以会返回调用方,然后打印相同的时间,一秒钟之后会打印执行完毕。

    image_thumb3_thumb

          Test3()调用了Task.Yeild()函数,主动让出执行权,所以会直接返回调用方,然后打印相同的时间,一秒之后会打印执行完毕。

    image_thumb5_thumb

          可以看到,开头的结论是正确的。那么,有什么意义呢?Yeild的意思在这里其实就是退让,让出的意思,让出什么呢?就是让出执行权,这与Thread.sleep(0)让出CPU执行权给其他线程(前提是有其他线程竞争)有机会执行是一个道理。

          请看我的例子:

     1 public static async Task OP1()
     2 {
     3      while (true)
     4      {
     5          await Task.Yield();//这里会捕捉同步上下文,由于是控制台程序,没有同步上下文,所以默认的线程池任务调度器变成同步上下文
     6                                      //也就是说后面的代码将会在线程池上执行,由于线程池工作线程数量设置为1,所以必须主动让出执行权,让其他的
     7                                      //任务有执行的机会
     8          Console.WriteLine("OP1在执行");
     9          Thread.Sleep(1000);//模拟一些需要占用CPU的操作
    10      }
    11 }
    12 public static async Task OP2()
    13 {
    14      while (true)
    15      {
    16          await Task.Yield();
    17          Console.WriteLine("OP2在执行");
    18          Thread.Sleep(1000);
    19      }
    20 }
    21 static async Task Main(string[] args)
    22 {
    23      ThreadPool.SetMinThreads(1, 1);
    24      ThreadPool.SetMaxThreads(1, 1);
    25      //Task.Run()方法默认使用线程池任务调度器执行任务,由于主线程不是线程池线程,所以使用Task.Run()
    26      var t = Task.Run(async () => 
    27      {
    28          var t1 = OP1();
    29          var t2 = OP2();
    30          await Task.WhenAll(t1, t2);
    31      });
    32      await t;
    33      Console.ReadLine();
    34 }

    image_thumb9_thumb

          可以看出OP1()和OP2()两个协程(Task)互相争用一个线程(用户模式下的CPU),如果不主动让出执行权,另一个协程(Task)将不会有机会执行。

          例如:

    1 public static async Task OP2()
    2 {
    3      while (true)
    4      {
    5          await Task.CompletedTask;//或者是直接去掉
    6          Console.WriteLine($"OP2在执行 {DateTime.Now}");
    7          Thread.Sleep(1000);
    8      }
    9 }

          这样OP1()将永远不会有机会执行。

    image20_thumb_thumb

  • 相关阅读:
    UVA-1595 Symmetry
    UVA-10763 Foreign Exchange
    剑指Offer
    剑指Offer
    剑指Offer
    剑指Offer
    剑指Offer
    剑指Offer
    剑指Offer
    剑指Offer
  • 原文地址:https://www.cnblogs.com/hkfyf/p/13276411.html
Copyright © 2011-2022 走看看