C# 委托 、事件、同步、异步知识点归纳
C# 委托 、事件、同步、异步知识点归纳
一、委托
基本用法:
1.声明一个委托类型。委托就像是‘类'一样,声明了一种委托之后就可以创建多个具有此种特征的委托。(特征,指的是返回值、参数类型)
public delegate void SomeKindOfDelegate(string result);
2.创建一个在1中创建的委托类型的委托。
public SomeKindOfDelegate aDelegate;
3.为2中创建的具体的委托添加响应函数。响应函数必须符合1中的‘特征'。
aDelegate +=new SomeKindOfDelegate(aFunctionThatJustForDelegate); private void aFunctionThatJustForDelegate(string result) { MessageBox.Show(result); }
4.完成以上三步之后,就可以使用Invoke来对委托进行调用了。Invoke可以选择调用的目标函数,调用优先级,以及调用的参数。
aDelegate.BeginInvoke("Hello~I'm being invoked!", null, null);
上面是基本用法,除了这种基本用法之外,还可以结合var、匿名委托、lambda委托等方法。
完整代码:
namespace wtfIsDelegate { public delegate void SomeKindOfDelegate(string result); public partial class Form1 : Form { public event SomeKindOfDelegate aDelegate; public Form1() { InitializeComponent(); aDelegate +=new SomeKindOfDelegate(aFunctionThatJustForDelegate); aDelegate.BeginInvoke("Hello~I'm being invoked!", null, null); } private void btnDelegate_Click(object sender, EventArgs e) { } private void aFunctionThatJustForDelegate(string result) { MessageBox.Show(result); } } }
委托的用处:
委托的优点,是可以实现异步(BeginInvoke),还可以在某种需要同时调用多个同参数、返回值的情况下简化代码。
二、事件
基本用法:
1.定义委托。
public delegate void SomeKindOfDelegate(string result);
2.定义事件。
public event SomeKindOfDelegate aDelegate;
3.为事件添加响应函数。
process.Exited += new EventHandler(CmdProcess_Exited);
4.为事件规定触发(调用)方式。(【也可以没有触发方式,直接invoke】)
解说:
C#里,每一种‘事件Event'大概都对应着其‘事件处理者EventHandler'。比如Process类的OutputDataReceived事件对应着DataReceivedEventHandler,对于非特异性的‘事件',比如PasswordChanged 这种,它们统一都对应着RoutedEventHandler或者EventHandler这种较为通用的‘事件处理者'。然而,‘EventHandler'也只是充当了一个中介的角色,真正触发了‘Event'之后要做什么,还需要我们手动指定,像这样:
process.Exited += new EventHandler(CmdProcess_Exited); // 注册进程结束事件 。
EventHandler本来也是委托。比如
public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);
自定义事件
自定义事件是一种类似于委托的方式,
自定义事件某种意义上改变了程序的流程,使得某个条件的变化由‘不断查询'转变为‘订阅与处理'的关系。
自定义事件需要有以下几个元素:
事件的发起者,事件的订阅,以及事件的处理程序。从发起者到处理程序之间是可以传参数的。
一个事件的‘发起'可以依赖于某种系统消息,比如‘OnKeyDown'、‘OnMouseClick'(【我目前还没见过这么写的源码】),也可以在某个条件达成时(比如两次输入了同样的字符)自行调用(其实收到系统消息也算是‘条件达成')。【更多的event是这么写出来的】
有些事件,并没有明显的‘发起者'。
委托和事件是啥关系
委托和自定义事件的使用方式十分十分类似。event 只有类的内部可以 Invoke,delegate的话,在哪都可以Invoke。调用的方式貌似也略有区别(传参方式)
由于调用方式和传参的差异,event显得更保守/稳定一些。event也更容易从‘理解'上更容易接受。
delegate貌似更多的用于进行异步(begin invoke)。而event则更多的用来做自定义事件。
委托和异步是啥关系
异步是委托可以实现的一种功能(或者叫做‘现象'也可以) 异步可以由很多种其他方式体现,比如多线程(thread,threadpool,task等等)。
多线程
.Net的委托本质上就是指向函数的指针,只不过这种指针是经过封装后类型安全的。委托和线程是两个不同的概念,线程是动态的,委托就是一个或一组内存地址,是静态的。线程执行时如果遇到了指向函数的指针就执行这个函数。
.Net为了方便编程,给委托赋予了两种方式以供调用线程来执行,即同步和异步方式,它们分别通过Invoke和BeginInvoke来开启。Invoke就是同步执行,由调用线程来执行,而BeginInvoke则开启了一个后台线程来执行delegate所指向的函数,这个后台线程和调用线程之间属于异步执行方式。实际上有了delegate这个概念,你在编程时就可以不用直接使用Thread类来开辟新的线程了,因为微软替你实现了。
使用BeginInvoke调用委托方法,其结果和调用一个新线程一样。多线程编程在开发中经常用到,比如将后台计算和UI更新主线程分离,防止界面卡顿等,着重关注线程池ThreadPool,因为Task任务就是整理了它。
Task
.NET 4包含新名称空间System.Threading.Tasks,它 包含的类抽象出了线程功能。Task 在后台使用ThreadPool。 任务表示应完成的某个单元的工作。 这个单元的工作可以在单独的线程中运行,也可以以同步方式启动一个任务,这需要等待主调线程。 使用任务不仅可以获得一个抽象层,还可以对底层线程进行很多控制。
在安排需要完成的工作时,任务提供了非常大的灵活性。 例如,可以定义连续的工作—— 在一个任务完成后该执行什么工作。 这可以区分任务成功与否。 另外,还可以在层次结构中安排任务。例如,父任务可以创建新的子任务。 这可以创建一种依赖关系,这样,取消父任务,也会取消其子任务。
启动一个Task
要启动任务,可 以使用 TaskFactory类 或 Task类 的构造函数和 Start()方法。Task类的构造函数在创建任务上提供的灵活性较大。
在启动任务时,会创建Task类 的一个实例,利用Action或Action<object>委托不带参数或带一个object参数 ,可以指定应运行的代码,这类似于Thread类 。下面定义了一个无参数的方法。 在实现代码中,把任务的ID写入控制台中:
static void TaskMethod() { Console.WriteLine("running in a task"); Console.WriteLine("Task id: {0}",Task.CurrentId); }
在上面的代码中,可 以看到启动新任务的不同方式。第一种方式 使用实例化TaskFactory类 ,在其中把 TaskMedlod()方 法传递给StartNew()方法,就会立即启动任务。 第二种方式使用 Task类的构造函数。 实例化 Task对象时,任务不会立即运行,而是指定 Created状态。接着调用 Task类的Start()方法,来启动任务。 使用Task类 时,除了调用 Start()方法,还可以调用RunSynchronously()方法。这样,任务也会启动,但在调用者的当前线程中它正在运行,调用者需要一直等待到该任务结束。 默认情况下,任务是异步运行的。
//using task factory TaskFactory tf = new TaskFactory(); Task t1 = tf.StartNew(TaskMethod); //using the task factory via a task Task t2 = Task.TaskFactory.StartNew(TaskMethod); //using Task constructor Task t3 = new Task(TaskMethod); t3.Start();
使用Task类的构造函数和TaskFactory类的StartNew()方法时,都可以传递TaskCreationOptions枚举中的值。设置LongRunning选项,可以通知任务调度器,该任务需要较长时间执行,这样调度器更可能使用新线程。如果该任务应关联到父任务上,而父任务取消了,则该任务也应取消,此时应设置 AuachToParent选项。PreferFairness的值表示,调度器应提取出已在等待的第一个任务。 如果一个任务在另一个任务内部创建,这就不是默认情况 。如果任务使用子任务创建了其他工作,子任务就优先于其他任务。 它们不会排在线程池队列中的最后。 如果这些任务应以公平的方式与所有其他任务一起处理,就设置该选项为PreferFairness。
Task t4 = new Task(TaskMethod, TaskCreationOptions.PreferFairness); t4.Start();
Action,Func等委托
泛型无返回值委托Action,有返回值Func,虽然使用Delegete委托可以达到同样的效果,但是用Action等泛型委托写法要更简洁。除了Delegate委托我们还可以使用Action<T>和Func<T>委托。
泛型Action<T>委托表示引用一个void返回类型的方法。Action<T>委托类存在不同的变体,可以传递至多16种不同的参数类型,没有泛型参数的Action类可以调用没有参数的方法。例如:Action<in T1>调用带一个参数的方法,Action<in T1,in T2>调用带两个参数的方法等
Func<T>的用法和Action<T>用法类似,但是Func<T>表示引用一个带返回类型的方法,Func<T>也存在不同的变体,至多可以传递16个参数类型和1个返回类型,例如:Func<in T1,out Resout>表示带一个参数的方法,Func<in T1,in T2,out Resout>表示调用带两个参数的方法。
下面就直接给一个Action<T>和Func<T>的例子
using System; namespace DelegateFuncAction { class Program { static void Main(string[] args) { Func<double, double,double> DoAddtion = calculate.addtion; double result = DoAddtion(20, 30); Console.WriteLine("Func带返回参数委托做加法结果为:{0}",DoAddtion(10,20)); calculate c=new calculate(); Action<double, double> DoSubstraction = c.substraction; DoSubstraction(90, 20); } } class calculate { public static double addtion(double x, double y) { return x + y; } public void substraction(double x, double y) { Console.WriteLine("Action不带返回参数委托做减法结果为:{0}",x-y); } } }