C# 异步编程深入理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiangqiang2015/article/details/82710104

目录

1、进程与线程

1.1 堆栈

1.2 CPU时间片

1.3 进程与线程

2、前台与后台线程

3、同步与异步

4、异步使用时机

5、参考地址:


网上已经有很多大牛对异步相关进行了深入的探讨,在此整合一下加入自己的理解学习下。

1、进程与线程

1.1 堆栈

内存格局通常分为四个区:

  全局数据区:存放全局变量,静态数据,常量

  代码区:存放所有的程序代码

  栈区:存放为运行而分配的局部变量,参数,返回数据,返回地址等,

  堆区:即自由存储区

线程堆栈(Thread Stack)和托管堆(Managed Heap)。

每个正在运行的程序都对应着一个进程(process),在一个进程内部,可以有一个或多个线程(thread),每个线程都拥有一块“自留地”,称为“线程堆栈”,大小为1M,用于保存自身的一些数据,比如函数中定义的局部变量、函数调用时传送的参数值等,这部分内存区域的分配与回收不需要程序员干涉。

堆栈中存放的是什么?

1.引用类型总是被分配到“堆”上。

2.值类型总是分配到它声明的地方:

   a.作为引用类型的成员变量分配到“堆”上

   b.作为方法的局部变量时分配到“栈”上

1.2 CPU时间片

时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

进程运行的CPU时间(CPU)=执行代码的CPU时间(USER)+系统调度所用的CPU时间(Sys)

1.3 进程与线程

进程是操作系统分配和使用系统资源的基本单位。

进程资源包括:

  • 一个进程堆;
  • 一个或多个线程;
  • 一个虚拟地址空间,该空间独立于其他进程的地址空间;
  • 一个或多个代码段,包括.dll中的代码;
  • 一个或多个包含全局变量的数据段;
  • 环境字符串,包含环境变量信息;
  • 其他资源,比如打开的句柄、其他的堆等;  

线程是处理器上系统独立调度和时间分配的最基本的执行单元。是轻量级进程,是进程的一个实体(线程本质上是进程中一段并发运行的代码)

线程间的数据交换有两种方式:

(1)共享内存方式shared memory(共享堆):最大的优势是快速

(2)消息传递方式message passing(不需要共享堆):优势在于安全

个人理解:

进程只是资源的一个边界,代码的执行靠CPU执行。而CPU的分配靠操作系统调度。所以要想把进程中的代码尽快执行完得需要操作系统高频率的分配CPU给该进程中的线程。

2、前台与后台线程

当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread),因为它是程序开始时就执行的,如果你需要再创建线程,那么创建的线程就是这个主线程的子线程,它是前台线程。 

新建的子线程可以是前台线程或者后台线程,前台线程必须全部执行完,即使主线程关闭掉,这时进程仍然存活。后台线程在未执行完成时,如果前台线程关掉,则后台线程也会停掉,且不抛出异常。也就是说,前台线程与后台线程唯一的区别是后台线程不会阻止进程终止。可以在任何时候将前台线程修改为后台线程。

3、同步与异步

个人理解是同步指在调用函数时,会等待被调用的函数执行完才会继续执行后面的代码。

异步就是在调用函数时,不用等被调用的函数执行完即可继续乡下执行。

如下:同步情况下函数3要等函数2执行完才能执行;如果函数2是异步执行的,函数3就可以和函数2并发执行了。但是异步不一定比同步执行的时间快,因为线程的调度和上下文切换也是很费时间的。

同步
异步

代码示例:(字符串拼接挺耗时的)

        /// <summary>
        /// 异步执行
        /// </summary>
        public void DoMainAsync()
        {
            DoSub1();
            var task= Task.Run(() => { DoSub1(); });   // 此处异步执行
            DoSub1();
            DoSub1();
            task.Wait();
        }

        /// <summary>
        /// 同步执行
        /// </summary>
        public void DoMainSync()
        {
            DoSub1();
            DoSub1(); // 此处同步执行
            DoSub1();
            DoSub1();
        }

        public void DoSub1()
        {
            string sResult = string.Empty;
            int iCount = 0;
            for (int i = 0; i < 50000; i++)
            {
                iCount += i;
                sResult += iCount.ToString();
            }
        }
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            //MainOperator.Instance.Domain();
            MainOperator.Instance.DoMainSync();
            stopWatch.Stop();
            Console.WriteLine(string.Format("同步共耗时:{0}", stopWatch.ElapsedMilliseconds));
            stopWatch.Start();
            MainOperator.Instance.DoMainAsync();
            stopWatch.Stop();
            Console.WriteLine(string.Format("异步共耗时:{0}", stopWatch.ElapsedMilliseconds));
            Console.Read();

耗时:

执行了很多次,同步一般在20000毫秒,异步一般41000毫秒。异步执行不一定比同步快,如果没用利用多核的特性,反而增加了系统调度的性能开销使程序执行的效率降低了。


示例二:(将第二步改造一下,执行数据库查询操作并读取出来,WHERE 条件不一样是防止数据库查询时走相同的执行计划)

        /// <summary>
        /// 异步执行
        /// </summary>
        public void DoMainAsync()
        {
            DoSub1();
            //var task= Task.Run(() => { DoSub1(); });
            var task = Task.Run(() => { DoSub2("SELECT fldm,ssqy FROM TEST WHERE 3=3"); });   // 此处异步执行
            DoSub1();
            DoSub1();
            task.Wait();
        }

        /// <summary>
        /// 同步执行
        /// </summary>
        public void DoMainSync()
        {
            DoSub1();
            //DoSub1();
            DoSub2("SELECT fldm,ssqy FROM TEST WHERE 2=2");  // 此处同步执行
            DoSub1();
            DoSub1();
        }

        public void DoSub1()
        {
            string sResult = string.Empty;
            int iCount = 0;
            for (int i = 0; i < 50000; i++)
            {
                iCount += i;
                sResult += iCount.ToString();
            }
        }

        public void DoSub2(string sSQL)
        {
            string sConnStr = string.Format(m_sOracleConnectionstring, "ORCL_XXXX", "test", "test");
            OracleConnection oracleConn = new OracleConnection(sConnStr);
            oracleConn.Open();
            OracleCommand command = oracleConn.CreateCommand();
            command.CommandType = System.Data.CommandType.Text;
            command.CommandText = sSQL;
            var reader = command.ExecuteReader();
            while (reader.Read())
            {
                var content = reader.GetValue(0);
            }
        }

耗时:

异步操作的时间减少了,而同步操作的时间反而增加了。说明让等待数据库查询的操作让其它线程去做,等待数据库并不消耗太多CPU执行,让CPU更多的去执行主线程的任务,提高了程序执行的效率。

4、异步使用时机

如果需要 I/O 绑定(例如从网络请求数据或访问数据库),则需要利用异步编程。 还可以使用 CPU 绑定代码(例如执行成本高昂的计算),对编写异步代码而言,这是一个不错的方案。

红框中的描述对上文中异步比同步执行还要耗时作出了解释。

5、参考地址:

堆栈:https://www.cnblogs.com/shenfengok/archive/2011/09/06/2169306.html

堆栈:https://blog.csdn.net/leewhoee/article/details/16933173

时间片:https://blog.csdn.net/qq_32812243/article/details/50590104

线程调度:https://www.cnblogs.com/x-xk/archive/2012/12/03/2795702.html

值和引用类型的存储:https://blog.csdn.net/itx2000/article/details/17240081

进程与线程:https://www.cnblogs.com/wjcx-sqh/p/6045013.html

前台后台线程:https://www.cnblogs.com/gudi/p/6233977.html

Microsoft .Net异步编程文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/async

 

猜你喜欢

转载自blog.csdn.net/xiangqiang2015/article/details/82710104