zoukankan      html  css  js  c++  java
  • 浅说C#异步和同步

    提到异步,那么与之对应的是什么呢?同步。那么C#的异步和同步是如何工作的呢?

    首先,我们先来看看栗子:

    新建一个控制台应用程序,在Program文件中添加如下代码:

     1  static void Main(string[] args)
     2         {
     3             //计时器
     4             Stopwatch watch = new Stopwatch();
     5             //开始计时
     6             watch.Start();
     7             Console.WriteLine($"{DateTime.Now.ToString()} 进入Main方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
     8             //调用任务一(同步)
     9             TaskOne();
    10             // 调用任务二
    11             TaskTwo();
    12             //停止计时
    13             watch.Stop();
    14             Console.WriteLine($"{DateTime.Now.ToString()} 退出Main方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
    15             Console.WriteLine($"主线程总耗时:{watch.ElapsedMilliseconds}ms");
    16             Console.ReadKey();
    17         }
    18         
    19         /// <summary>
    20         /// 任务一
    21         /// </summary>
    22         static void TaskOne()
    23         {
    24             Console.WriteLine($"{DateTime.Now.ToString()} 进入TaskOne方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
    25             for (int i = 0; i < 5; i++)
    26             {
    27                 Console.WriteLine($"{DateTime.Now.ToString()} TaskOne正在执行,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
    28                 System.Threading.Thread.Sleep(1000);
    29             }
    30             Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskOne方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
    31         }
    32         /// <summary>
    33         /// 任务二
    34         /// </summary>
    35         static void TaskTwo(){
    36             Console.WriteLine($"{DateTime.Now.ToString()} 进入TaskTwo方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
    37             for (int i = 0; i < 2; i++)
    38             {
    39                 Console.WriteLine($"{DateTime.Now.ToString()} TaskTwo正在执行,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
    40                 System.Threading.Thread.Sleep(1000);
    41             }
    42             Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskTwo方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
    43         }

    这个栗子很简单,定义了两个方法:TaskOne,TaskTwo。在里面每隔一秒输出一次当前时间,和当前线程。TaskOne循环5次和TaskOne2次。然后在MAIN函数里面顺序调用,并记录MAIN函数执行的总耗时时间。F5运行效果如图:

    从图中可以看出,程序顺序执行TaskOne之后,再执行TaskTwo。执行线程未改变。

    下面我们改改代码,用异步方式改写下TaskOne。提到异步,大家脑海里随之浮现的我想会是它吧。关键字async。当然与之成对出现的await也不能少了。先看看改写后的代码:

     /// <summary>
            /// 任务一(异步)
            /// </summary>
            /// <returns></returns>
            static async Task<int> TaskOneAsync()
            {
                Console.WriteLine($"{DateTime.Now.ToString()} 进入TaskOneAsync方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
                var t = Task<int>.Run(() =>
                {
                    var total = 0;
                    for (int i = 0; i < 5; i++)
                    {
                        total++;
                        Console.WriteLine($"{DateTime.Now.ToString()} TaskOneAsync正在执行,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
                        System.Threading.Thread.Sleep(1000);
                    }
                    return total;
                });
                Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskOneAsync方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
                return await t;
            }

    main函数改为调用异步方法

     static void Main(string[] args)
            {
                //计时器
                Stopwatch watch = new Stopwatch();
                //开始计时
                watch.Start();
                Console.WriteLine($"{DateTime.Now.ToString()} 进入Main方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
                //调用任务一(同步)
                //TaskOne();
                //调用任务一(异步)
                TaskOneAsync();
                // 调用任务二
                TaskTwo();
                //停止计时
                watch.Stop();
                Console.WriteLine($"{DateTime.Now.ToString()} 退出Main方法,执行线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
                Console.WriteLine($"主线程总耗时:{watch.ElapsedMilliseconds}ms");
                Console.ReadKey();
            }

    F5运行后效果:

    我们可以看到Main函数的执行时间从7082ms变为了2404ms。时间大大的缩短了。但是,在main已经结束后,TaskOneAsync依然还在运行中....,并且TaskOneAsync的执行线程不是主线程9而是10!!

    下面我们来好好梳理下程序的执行过程,看图便知:

    可以看到当程序进入Main方法执行,进入TaskOneAsync后,线程ID依然是9,当遇到Task执行任务创建,并运行。主线程并没有阻塞,而是单独开了一个新的线程10去执行TASK任务。主线程依然顺序执行,然后退出异步方法。进入到TaskTwo中执行完毕,最后直到Main方法结束时,由于TaskOneAsync耗时较长,线程10依然继续在执行Task。直到Task结束。其实系统,在Task任务Run的时候,已经新开了一个线程执行Task里面的任务,然后主线程继续执行TaskTwo,在TaskTwo执行这段期间,任务TaskOneAsync也在另一个线程同时j执行。可见,Task会新开一个线程执行命令,当前线程不会被阻塞,因此Main线程其实根本没有像同步方式一样执行最耗时的TaskOneAsync里面的Task,而是交给了另外一个线程执行,这就是主线程执行时间,大大缩短的原因。因此,这种处理机制,对于用户体验,是比较友好的。其实,在我们开发中,常常见到以async结尾的方法。最常见的应该是IO读取,写入,以及 http资源请求相关类库方法。因为这些都是比较耗时的,一般耗时的工作,为了不影响主线程响应,我们一般都采用异步方式进行处理。

    那么,当我们主线程,需要获取Task任务返回结果时,主线程会阻塞线程等待其结果返回后,再继续执行下去。改下Main方法里面的代码,验证一下:

     如图,得以验证,主线程阻塞了线程,等待Task执行完毕后,再继续执行。

    归纳总结,异步和同步,我是这样理解的:

    同步:一段代码指令,在同一线程上,被顺序执行,中间没有插队。就好比去电影院买票,有很多人(待执行的指令),但是只有一个窗口(一个线程,一般指主线程)。后面的人,只能等前面的人买了票,走了,才能前一步,他们的步调是一致。所以,称之为同步。

    异步:一段代码指令,在执行的时候,其中一些指令与指令之间,被执行的时间点一样,但是操作其执行的线程不一样。两者存在一段时间的并行现象。好比电影院看到排队买票的人越来越多,经理马上又新安排了一个售票员开了一个新窗口(开新线程)售票,把原来排队的人(待执行的指令),转移了一部分到新的窗口继续排队买票。这样原来售票窗口(主线程)的作业任务以及时间,则相应减少了。

    异步方式其实是一种处理机制,它有好处,也有弊端。如果我们无端的滥用,会起反作用。因为,新开线程会消耗线程资源。所以,秉承一个原则:在不影响主线程响应前提下,能不用则不用。

    以上都是个人见解,如有错误,还望指出,望不吝赐教~~

  • 相关阅读:
    [P1034][NOIP2001]一元三次方程求解 (二分)
    考前停课集训 Day7 嘞
    [P4995]跳跳!(贪心)
    [P4994]终于结束的起点 (递推)
    考前停课集训 Day6 垒
    [BZOJ1899][ZJOI2004]Lunch 午餐 (DP)
    考前停课集训 Day5 累
    任务查询系统 【主席树】
    主席树入门
    HNOI2002 营业额统计 平衡树模板题 【splay】
  • 原文地址:https://www.cnblogs.com/paulcode/p/8489304.html
Copyright © 2011-2022 走看看