• 【概念】多线程汇总(线程、委托、事件)


    一、多线程

    1、线程概念

    线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

    一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

    概要:

    1、线程是轻量级的进程

    2、线程没有独立的地址空间(内存空间)

    3、线程是由进程创建的(寄生在进程)

    4、一个进程可以拥有多个线程-->这就是我们常说的多线程编程

    5、线程有几种状态:  

      a、新建状态(new)  

      b、就绪状态(Runnable)  

      c、运行状态(Running)  

      d、阻塞状态(Blocked) 

       e、死亡状态(Dead)

    当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。或通过线程锁来进行线程同步。

    实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
    其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

    2、异步编程

    在很多时候,我们在进程中使用单一线程从头到尾地执行程序,这种简单模式会导致性能和用户体验另人难以接受。

    比如程序向另外一台服务器发出请求,由于网络等外部原因,此种通信任务往往会耗费大量时间。
    进程如果在此期间仅仅只能等待网络或网络上其他机器的响应,将严重地降低了性能。
    程序不应该浪费等待的时间,而应该更加高效地利用,在等待的时间执行其他任务,回复到达后在继续执行第一个任务。
    如果程序调用某个方法,等待其执行全部处理后才能继续执行,我们称其为同步的。
    相反,在处理完成之前就返回调用方法则是异步的。
    我们在编程语言的流程中添加了异步控制的部分,这部分的编程可以称之为异步编程
     
     

    3、学习网址

    一个总结帖,也是这篇整理的大部分出处:

    https://www.cnblogs.com/wyt007/p/9486752.html

    4、线程锁lock

    1)说说lock到底锁谁?

     https://blog.csdn.net/weixin_34344403/article/details/85686895

    2)死锁

    https://www.cnblogs.com/liuqiyun/p/9118382.html

    3)分解
    实际上lock关键字是Monitor类用例的一个语法糖。如果我们分解使用了lock关键字的代码,将会看到它如下面代码片段所示:
                bool acquiredLock = false;
                try
                {
                    Monitor.Enter(lockObject, ref acquiredLock);
                }
                finally
                {
                    if (acquiredLock)
                    {
                        Monitor.Exit(lockObject);
                    }
                }
    我们可以直接使用Monitor类。其拥有TryEnter方法,该方法接受一个超时参数。
    如果在我们能够获取被lock保护的资源之前,超时参数过期,则该方法会返回 false.

    5、线程间传递参数

    1)跨线程控制控件:

    https://blog.csdn.net/xz_life/article/details/8446129

    2)主线程和子线程如何实现互相传递数据(三种方式):

    https://www.cnblogs.com/eve612/p/14342087.html

    https://www.cnblogs.com/stemon/p/4214906.html

    https://blog.csdn.net/shuaihj/article/details/41316731


    一般使用lambda表达式。
    这可能会导致几个问题。例如,如果在多个lambda表达式中使用相同的变量,它们会共享该变量值。
    如下代码:
                int i = 10;
                var t1 = new Thread(() => PrintNumber(i));
                i = 20;
                var t2 = new Thread(() => PrintNumber(i));
                t1.Start();
                t2.Start();

    两个线程都会打印出20,因为t1在线程启动之前变量已经被修改为20了。

    多线程与委托密不可分,详细委托相关知识见本文章末尾。

    6、线程越多越好吗?

    线程不是越多越好,还得看内存能不能带动(抢占资源)。

    并且线程创建和销毁都要消耗很多时间的,如果创建消耗的时候大于执行任务的时候,就没有必要多线程了。

    同时cpu频繁在各个线程之间切换影响性能(开销大)。

    https://blog.csdn.net/yusimiao/article/details/105378311?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

    7、线程池

    当我们需要频繁的创建线程时,我们要考虑到通过线程池统一管理线程资源,避免不可控风险以及额外的开销。

    归纳起来说,线程池的作用包括:

        实现任务线程队列缓存策略和拒绝机制
        实现某些与实践相关的功能,如定时执行,周期执行等(比如列车指定时间运行)
        隔离线程环境,比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大。因此,通过配置独立的线程池,将较慢的交易服务与搜索服务个离开,避免个服务线程互相影响

        利用线程池管理并服用线程,控制最大并发数(手动创建线程很难得到保证)

    8、前台线程和后台线程

    当主程序启动时定义了两个不同的线程。通过手动设置IsBackground属性为ture来创建后台线程。


    通过配置来实现前台线程会比后台线程先完成。然后运行程序。前台线程完成后,程序结束并且后台线程被终结。

    这是前台线程与后台线程的主要区别:
    进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。

    一个重要注意事项是如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。

    9、终止线程


    1)调用Abort方法终止线程的原理:给线程注入ThreadAbortException方法,导致线程被终结。

    隐患:

    1、该异常可以在任何时刻发生并可能彻底摧毁应用程序。
    2、使用该技术也不一定总能终止线程。目标线程可以通过处理该异常并调用Thread.ResetAbort方法来拒绝被终止。
    因此并不推荐使用Abort方法来关闭线程。可优先使用一些其他方法。

    2)中止线程的方法

    (1)判断标志位。
    https://blog.csdn.net/zhu2695/article/details/53464757

    (2)提供一个CancellationToken方法来取消线程的执行。这个我没有试过,马一下。

    https://www.tnblog.net/aojiancc2/article/details/58

    https://blog.csdn.net/yangwohenmai1/article/details/90404497?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-7&spm=1001.2101.3001.4242



    10、处理异常
    (1)如果在线程外添加异常捕获代码:
            try
            {
                t = new Thread(BadFaultyThread);
                t.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine("We won't get here!");
            }
    则异常不会被包裹启动线程的try/catch代码块捕获到。
    所以如果直接使用线程,一般来说不要在线程中抛出异常,而是在线程代码(BadFaultyThread)中使用try/catch代码块。


    (2)跨线程调用控件时关闭窗体时如何避免出现异常:Cannot access a disposed object

    https://www.cnblogs.com/beilinyu/p/3569123.html

    二、委托和事件

    1、委托(Delegate)

    (1)概述

    委托是一个类,它使得可以将方法当作另一个方法的参数来进行传递

    将方法动态地赋给参数,可以避免在程序中大量使用 if else (switch)语句,同时使得程序具有更好的可扩展性。

    委托的意义:
    1.任何异步多线程都是与委托有关的。
    2.把方法当作参数来传递。
    3.实现方法逻辑分离。
    4 委托的意义:解耦(将公共逻辑提取,私有部分使用委托将可执行方法传进来)
    5 委托的意义:异步多线程
    6 委托的意义:多播委托
     

    委托的三种调用示例(同步调用、异步调用、异步回调):

    https://www.cnblogs.com/linybo/p/10126686.html

     ○ 触发委托有2种方式: 委托实例.Invoke(参数列表),委托实例(参数列表);

    回调函数简述:https://blog.csdn.net/sajiazaici/article/details/78702144

    最主要的两个作用:封装、异步

    (2)delegate和Invoke的区别:

    delegate是委托,本身不能解决跨线程访问控件的问题,直接调用委托还是会报错。
    Invoke指定用主线程中的控件去调用这个委托,相当于主线程来执行这个函数。

    2、事件

    (1)概念

    事件基于委托模型。
    委托模型遵循观察者设计模式,使订阅者能够向提供方注册并接收相关通知。

    事件是用委托实现的,是对委托的额外封装,其本质上是一种特殊的委托。

    事件分为两个阶段:
    委托的实例化(对应事件订阅)
    委托的调用(对应事件触发)


    (2)原理

    1)引发事件
    事件是由对象发送的用于发出操作信号的消息。
    该操作可能是由用户交互引起,例如单击按钮;
    也可能是由某些其他程序的逻辑导致,例如更改属性值。
    引发事件的对象称为“事件发送方”。

    事件发送方推送事件发生的通知。事件发送方不知道哪个对象或方法将接收(处理)它引发的事件。
    事件接收器接收该通知并定义对它的响应。

    事件通常是事件发送方的成员。
    例如 Click 事件是 Button 类的成员;
    PropertyChanged 事件是实现 INotifyPropertyChanged 接口的类的成员。


    为了引发事件,可以在 C# 中添加一个标记为 protected 和 virtual 的方法。
    将此方法命名为 OnEventName;例如,OnDataReceived。

    此方法应接受一个指定事件数据对象(EventArgs 类型或派生类型)的参数。
    您提供此方法以允许派生类重写引发事件的逻辑。

    1     public event EventHandler ThresholdReached;//声明名为 ThresholdReached 事件
    2 
    3     protected virtual void OnThresholdReached(EventArgs e)
    4     {
    5         EventHandler handler = ThresholdReached;
    6         handler?.Invoke(this, e);//该事件与 EventHandler 委托相关联并且由 OnThresholdReached 方法引发。
    7     }


    在事件上下文中,委托是事件源和处理事件的代码之间的媒介(或类似指针的机制)。

    .NET提供了 EventHandler 和 EventHandler<TEventArgs> 委托来支持大部分事件场景。

    使用 EventHandler 委托处理不包含事件数据的所有事件。

    使用 EventHandler<TEventArgs> 委托处理包含事件相关数据的事件。

    这些委托没有返回类型值,并且接受两个参数(事件源的对象和事件数据的对象)。

    在 EventHandler 和 EventHandler<TEventArgs> 委托不可用的场景下,可以定义一个委托。
       

       //声明 ThresholdReachedEventHandler 委托
       public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);


    与事件相关的数据可以通过事件数据类提供。
    .NET 遵循所有事件数据类以 EventArgs 结尾的命名模式。

    例如,SerialDataReceivedEventArgs 类是 SerialPort.DataReceived 事件的事件数据类。

    通过查看事件的委托来确定哪个事件数据类与事件相关联。

    例如,SerialDataReceivedEventHandler 委托包含 SerialDataReceivedEventArgs 类作为它的一个参数。


    2)响应事件
    若要响应事件,需要在事件接收器中定义一个事件处理程序方法。
    此方法必须与您正处理的事件的委托签名匹配。

        static void Main()
        {
            var c = new Counter();
            c.ThresholdReached += c_ThresholdReached;//事件处理程序方法
        }
    
        static void c_ThresholdReached(object sender, EventArgs e)
        {
            Console.WriteLine("The threshold was reached.");
        }

    出处:

    https://docs.microsoft.com/zh-cn/dotnet/standard/events/

    3、委托和事件的联系和区别
    ○ 委托就是一个类,也可以实例化,通过委托的构造函数来把方法赋值给委托实例。事件不需要实例化;
    ○ 事件可以看作是一个委托类型的变量;
    ○ 通过+=为事件注册多个委托实例或多个方法;通过-=为事件注销多个委托实例或多个方法;
    ○ EventHandler就是一个委托。

    https://www.cnblogs.com/darrenji/p/3967381.html

    简述委托与事件的区别:

    https://blog.csdn.net/lrfleroy/article/details/88780590

    4、回调函数

    https://blog.csdn.net/weixin_33994429/article/details/86069163

    /*******相与枕藉乎舟中,不知东方之既白*******/
  • 相关阅读:
    encodeURIComponent编码时为什么要编码两次
    JS校验身份证号的合法性
    react-router与react-router-dom使用时的区别
    数组去重
    window的cmd命令行下新增/删除文件夹及文件
    数组排序【冒泡排序、快速排序、选择排序】
    个人搭建后台管理模板 Bootstrap4 ,ASP.NET Core,EF Core,JWT
    个人搭建后台管理模板 Bootstrap4 ,ASP.NET Core,EF Core,JWT
    react-starter-projects
    基于H.ui.Admin UI模板的网站管理后台
  • 原文地址:https://www.cnblogs.com/Mars-0603/p/13595154.html
Copyright © 2020-2023  润新知