异步

说到异步,其实这并不是软件的概念,而是计算机的概念,而与之对应的就是同步了,相信大多数编程人员对这两个词都不陌生。
首先我们先明确几个概念,线程,进程,多线程,以及并行。
线程:可以理解为计算机执行的最小单位。
进程:可以理解为是维持某一个应用程序的线程集合,线程包括一个甚至是多个线程。
多线程:指的是多个线程并发执行进行工作。
并行:指的是多个线程包括主线程一起执行工作,其实并行是多线程的一种。
下图可以看到一般情况下线程的数量大都是大于进程的数量的。
在这里插入图片描述
之所以现在要学习异步和多线程,主要是因为在项目中有时候,只是操作单独一根子线程有时候并不是很方便,面对不同的需求,有时候不得不使用多个线程去做不同的事情,来提高系统的性能。

同步

同步只有一根主线程,主线程从上到下去按顺序执行代码。下图显示的就是同步的方法,编程人员其实一开始接触的便是同步,只不过大多数初学者还没有这个概念而已,所以在这里对于同步就不过多的介绍了。

      List<Student> students = GetStudentList();
            Console.WriteLine("创建数组完毕");
            List<Student> newStudents = new List<Student>();
            同步的方法
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < 10; i++)
            {
                var stus1 = students.Where(c => c.ID > 500 && c.ID < 999999).ToList();
                newStudents.AddRange(stus1);
            }
            stopwatch.Stop();
            Console.WriteLine("查询得到的学生的个数" + newStudents.Count);
            Console.WriteLine("主线程的ID是" + Thread.CurrentThread.ManagedThreadId + "耗时毫秒" + stopwatch.ElapsedMilliseconds);
异步

.NET Framework 允许您异步调用任何方法。定义与您需要调用的方法具有相同签名的委托,公共语言运行库(CLR)将自动为该委托定义具有适当签名。
所谓异步其实也是多线程的一种。在C#中无论是使用异步还是使用多线程,都不得不使用委托,这是因为异步其实是会开启一根子线程去执行任务,而在C#这门面向对象的语言中,委托或者函数(方法)则更像是一件事情,可以理解为是一个任务,这就像需要让一个人做一件事情一样。
在c#中可以通过BeginInvoke ()来启动一个异步,调用 BeginInvoke 后可随时调用 EndInvoke() 方法;如果异步调用未完成,EndInvoke 将一直阻塞到,如果委托是由返回值的,则EndInvoke() 将带回委托的返回值。下面的代码是展示的是启动带有返回值的委托,并取得其结果:

      try
            {
                stopwatch.Restart();
                Func<List<Student>, List<Student>, List<Student>> func = Test;
                IAsyncResult asyncResult = func.BeginInvoke(students, newStudents, ar =>
                  {
                      Console.WriteLine("回调函数");
                  }, "dasd");
                asyncResult.AsyncWaitHandle.WaitOne();
                stopwatch.Stop();
                Console.WriteLine("查询得到的学生的个数" + newStudents.Count);
                Console.WriteLine("主线程的id是" + Thread.CurrentThread.ManagedThreadId + "耗时毫秒" + stopwatch.ElapsedMilliseconds);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

其是在这个方法中,有一点是需要注意的就是newStudents.AddRange(func.EndInvoke(asyncresult));
就是可能有些人会认为这里存在一个线程安全的问题,其实并不是这样的,我们循环创建的每一个异步,其实都对应着一个EndInvoke()的方法,这样就不存在什么线程安全的问题了。

   public static List<Student> Test(List<Student> students,List<Student> newStudents)
        {
            for (int i = 0; i < 10; i++)
            {
                Func<List<Student>, List<Student>> func = WhereStudents;
                IAsyncResult asyncresult = func.BeginInvoke(students, argumenst =>
                {
                    Console.WriteLine("子线程线程的id是" + Thread.CurrentThread.ManagedThreadId);
                }, $"编号{i}");
                newStudents.AddRange(func.EndInvoke(asyncresult));
            }
            return newStudents;
        }

看执行的结果,根据结果我们可以看到异步和同步查询到学生的数量都是一样的,而且在耗时方面异步的耗时比同步的耗时更少,毕竟多个人比一个人做一件事件效率更高。另外我们也可以发现在异步中除了编号为1的主线程外,还有子线程5和6。为什么我们启动了10个异步却只有2个子线程?其实在循环的时候,主线程遇见了异步操作只会向OS(操作系统)发出一个信号,然后主线程就走开了,OS接到信号后,会去找线程池去取线程,由于我们的代码只是做了一个简单的查询,子线程做的事情比较少,导致主线程在循环到某一步时,子线程已经执行完任务,被放回到了线程池,而下次需要子线程执行任务的时候,OS又将刚刚回到线程池的线程又派出去执行任务去了。
在这里插入图片描述

异步顺序的控制

无论是在异步还是在多线程中,都不要去使用线程ID或者是通过Sleep()方法来控制,因为这都是很不靠谱的,也许这一次能行,但是下一次就未必了。
回调函数
对于异步来说,每次执行玩一个异步都会执行一个回调函数

   IAsyncResult asyncresult = func.BeginInvoke(students, argumenst =>
                {
                    Console.WriteLine("子线程线程的id是" + Thread.CurrentThread.ManagedThreadId);
                }, $"编号{i}");

这里的lamdba表达式其实就是一个回调函数,可以通过回调去对异步进行一些控制。
EndInvoke ()函数
BeginInvoke对应的就是EndInvoke ,它既可以标识一个异步的完成,也可以得到异步的返回值,如果异步一直未结束,那EndInvoke 就会被一直堵塞。
IsCompleted属性
每一个异步执行完毕后都会得到一个
AsyncResult
的结果,我们可以通过判断asyncresult.IsCompleted()来检测异步是否已经结束了,但是不推荐这种,因为如果异步为未执行完毕,主线程则会一直等待,如果是window程序则会造成界面的卡死

         if (asyncResult.IsCompleted)
                {
                    Console.WriteLine("子线程还没执行完毕");
                }
                Console.WriteLine("子线程已经执行完毕了");

AsyncWaitHandle.WaitOne函数
这个方法与第三个方法类似,当子线程未执行完毕时,主线程都会被堵塞,但是不同的是AsyncWaitHandle.WaitOne() 函数可以传递一个毫秒数进去,也就是说我们可以让主线程等待一段时间,如果主线程等待完毕有,子线程未执行完毕,那么主线程也会继续执行任务,这样我们就可以通过这个函数进行一些超时的控制。

 Func<List<Student>, List<Student>, List<Student>> func = Test;
                IAsyncResult asyncResult = func.BeginInvoke(students, newStudents2, ar =>
                  {
                      Console.WriteLine("回调函数");
                  }, "dasd");
                asyncResult.AsyncWaitHandle.WaitOne();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值