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

一、多线程

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

/*******相与枕藉乎舟中,不知东方之既白*******/
原文地址:https://www.cnblogs.com/Mars-0603/p/13595154.html