• 20181103_C#线程初探, BeginInvoke_EndInvoke


    在C#中学习多线程之前, 必须要深刻的理解委托; 基本上所有的多线程都在靠委托来完成

    一.   进程和线程:

      a) 进程和线程都是计算机的概念, 跟程序语言没有任何关系

      b) 进程和线程都属于计算机操作系统自身控制和调度, 程序语言只有使用的份, 最终的控制权还是得操作系统说了算, 程序语言最多有提醒功能, 比如叫线程休眠/挂起/终止,    至于操作系统听不听, 做不做, 什么时候做, 那是操作系统高兴不高兴的事.

      c)  在任务管理器中, 下图每一个都是一个进程

    d) 在任务管理器中, 性能中可以看到线程:

     

      e)进程: 一个程序运行时, 占用计算机所有资源的总和; (CPU/内存/磁盘/GPU/IO)

      f) 线程: 程序执行流的最小单位, 任何操作的执行都是由线程完成的, 线程是依托于进程存在的, 一个进程可以包含多个线程, 线程也可以有自己的计算资源

      g)多线程: 多个执行流同时执行

    二.   同步异步多线程:

      a)同步和异步都是对方法的描述

        i. 同步:

      1.  在一个方法体内一步一步的按照代码编写的顺序(分支/循环)来依次执行;
      2.  同步方法卡界面,主(UI)线程忙于计算
      3.  同步方法慢,只有一个线程干活
      4.  action.Invoke();→属于同步调用

     ii.异步:

    1. 不会等待方法的完成,会直接进入下一行  非阻塞;
    2. 异步多线程方法不卡界面,主线程完事儿了,计算任务交给子线程在做;
    3. 异步多线程方法快,因为多个线程并发运算
    4. 应用: winform提升用户体验;web一个业务操作后要发邮件,异步发送邮件 ;  action.BeginInvoke()→属于异步调用

     iii.  多线程, 启动无序, 执行时间不确定, 结束无序

    三.   同步异步和多线程:

    a) 同步异步是方法执行的概念

    b) 线程/进程属于计算机概念

    四.   示例和代码:

    所有演示都用到的DoSomething方法代码如下:

    /// <summary>
            /// 一个比较耗时耗资源的私有方法
            /// </summary>
            /// <param name="name"></param>
            private void DoSomethingLong(string name)
            {
                Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
                long lResult = 0;
                for (int i = 0; i < 1000000000; i++)
                {
                    lResult += i;
                } 
    
                Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
            }
    

    a) BeginInvoke属于异步调用, 当任务完成之后, 使用当前的线程调用CallBack指定的动作;   再说一遍: BeginInvoke是当 当前线程 完成 后, 再用当前的线程来执行callback的任务; 下面代码演示

    Action<string> action = this.DoSomethingLong;
    
                IAsyncResult asyncResult = null;
                //异步回调
                AsyncCallback callback = ia =>
                {
                    //Console.WriteLine(object.ReferenceEquals(asyncResult, ia)); //true
                    //Console.WriteLine(ia.AsyncState); //ia.AsyncState  这个值就是传递过来的hao; 可以看做是参数
                    //Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
                };
                //对于BeginInvoke的理解: 相当于.net框架为我们做了一个小封装, 当使用BeginInvoke的时候, .net框架从线程池, 拿一个线程出来执行beginInvoke, 当beginInvoke执行完成之后,        //产生一个变量(asyncResult), 作为一个结果来描述这个线程, 然后这个变量又会作为一个参数来调用callback并执行, 所以当你使用object.ReferenceEquals(asyncResult, ia)发现结果是个true
    	    //后面这个hao, 表示状态参数, 也就是说如果要在回调的时候使用某些信息, 则可以通过这个参数进行传递

            //"btnAsyncAdvanced_Click" 是传递给调用者的参数; 比如 Action<string> a = DoRunData; a.BeginInvoke("abc", null, a);那么

            // DorunData(string s); 这个函数在被调用时, 参数的值就是 abc
    asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "hao"); Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");

    b) 启动多线程去并行计算, 但是主线程又得真的等待到主线程把事情做完之后, 才能返回计算结果, 进行后续的执行, 下面代码演示使用asyncResult.IsCompleted完成等待:

     IAsyncResult asyncResult = null;
                //异步回调
                AsyncCallback callback = ia =>
                { 
    Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
                }; 
                asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "hao");
    
                Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
    
                int i = 0; //IsCompleted判断异步操作是否完成
                while (!asyncResult.IsCompleted)//1 卡界面:主线程忙于等待
                {   //可以等待,边等待边做其他操作 ; 做一点用户提示
                    //可能最多200ms的延迟
                    if (i < 10)
                    {
                        Console.WriteLine($"文件上传完成{i++ * 10}%..");//File.ReadSize
                    }
                    else
                    {
                        Console.WriteLine($"文件上传完成99.9%..");
                    }
                    Thread.Sleep(200);
                }
                Console.WriteLine($"上传成功了。。。..");
    

    c)下面的代码演示使用asyncResult.AsyncWaitHandle.WaitOne(1000);进行等待

     Action<string> action = this.DoSomethingLong;
                IAsyncResult asyncResult = null;
                //异步回调
                AsyncCallback callback = ia =>
                {
                     Console.WriteLine("我在callback里. . .");
                }; 
                asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "hao"); 
    
                Thread.Sleep(300);
                Console.WriteLine("执行其他的代码. . .");
                Console.WriteLine("执行其他的代码. . .");
                Console.WriteLine("执行其他的代码. . .");
    
                asyncResult.AsyncWaitHandle.WaitOne();//等待任务的完成 ; 上面的方式查看状态的等待事件是否完成, 这种方式是通过信号量来实现的
                asyncResult.AsyncWaitHandle.WaitOne(-1);//等待任务的完成; -1这个参数和不传递参数是一样的, 不传递参数也表示一直等待, 传递-1也表示一直等待
                asyncResult.AsyncWaitHandle.WaitOne(1000);//限时等待;但是最多等待1000ms; 比如调用远程接口, 超过什么时间就不再等待
    

    d)下面的代码演示使用EndInvoke来等待, 并获取返回值; 在用BeginInvoke异步调用方法时, EndInvoke方法会一直阻塞,等待被调用的方法执行完毕.

       Func<int> func = () =>
                    {
                        Thread.Sleep(2000);
                        return DateTime.Now.Day;
                    };
                    Console.WriteLine($"func.Invoke()={func.Invoke()}");
    
                    IAsyncResult asyncResult1 = func.BeginInvoke(r =>
                    {
                        func.EndInvoke(r); //通常来讲, EndInvoke会写在BeginInvoke的里面, 表示只调用一次; 另外注意: EndInvoke只能调用一次. 这一行和下面一行只能存在一个
                        Console.WriteLine(r.AsyncState);
                    }, "孙悟空");
    //Console.WriteLine($"func.EndInvoke(asyncResult)={func.EndInvoke(asyncResult1)}");
    

    e) 三种线程等待方式的总结:

    //1. asyncResult.IsCompleted 可以边等待, 边做其他的事情
    //2. asyncResult.AsyncWaitHandle.WaitOne(1000); 可以做限时等待
    //3. action.EndInvoke(asyncResult)可以做返回值等待
    

      

  • 相关阅读:
    Docker入门系列4:命令行小结
    Docker入门系列3:使用
    Docker入门系列2 安装
    Docker入门系列1:简介
    在VMware下安装CentOS系列1:配置VMware
    bugzilla 系列1安装
    查看SELinux状态并关闭SELinux
    Qt 编程指南 5 丰富文本编辑控件
    Qt 编程指南 4 单行编辑控件
    Qt 编程指南 4 按钮2 打开网页和文件夹
  • 原文地址:https://www.cnblogs.com/wxylog/p/9902013.html
Copyright © 2020-2023  润新知