C#委托、事件、多线程、Task浅析
前言:最近看Xamarin中默认的项目代码,发现使用了大量的Task类,因此回过头来总结一下C#中委托、事件、多线程、Task在开发中的应用的应用,首先提一句看到一系列博客写的特别好,地址是:http://www.cnblogs.com/sosowjb/archive/2012/08/11/2633953.html 。一共有6篇内容。我这篇主要内容就是总结自己用过的,尽可能写出一些机理性的东西,更好理解。
委托delegate
委托的字面意思就是请别人帮忙,它也确实是干这个事的。如果对C++比较了解,委托比较像指向类或者指向方法的指针。在C++中我们可以这样:定义一个基类,在基类中定义方法的原型(无具体内容),在派生中具体写方法。定义一个指向这个基类、派生类实例中方法的指针,通过指针直接访问类中的方法。委托也基本上做了上面的事情,不过过程要简单很多,你已经看不见指针了。
为什么要使用委托?
先定义方法的原型,在外部写原型中方法的具体内容,然后调用执行。想要做到这样,就要用到委托(关于泛型委托的问题以后再说),在C#中所有的自定义事件都是用委托来实现的。先看看自定义事件要做个什么事:比如我有一个自定义控件,这个控件里面有一个自定义事件,当事件被触发的时候要执行一个方法,这个方法是在类外部定义的,当我们使用这个控件的时候才会确定这个自定义事件所执行的方法,怎么样明白了吧,这不就是委托么?这不就是C++里面指向类的函数的指针吗?
在C#委托是一个独立的存在,理解上可以看做C++的基类,关于委托的使用看前言里提到的博客就可以了,这里我直接转载。首先定义一个委托,可以有返回值也可以无返回值。
public delegate string MyDelegate(int ms);
例子中有返回string,输入为int类型的ms,这样就定义了一个委托,这个委托的输入输出都定义了,但是...这个委托是干什么的要在外部定义,这就是委托存在的意义!就好像我新建一个委托是一个快递员,我们知道他可以送快递,但是送什么快递送到哪里是由用户决定的,这个快递员就是委托,他不能决定送什么快递,这就是委托(嗯,这个例子不错!)
随后定义委托的方法,对应上面的例子我们给快递员描述一下快递送到哪里,对应C++就是给基类指向方法的指针赋值。
static string DelegateMethod(int ms) { Console.WriteLine("把快递送到火星"); Thread.Sleep(ms); Console.WriteLine("已到达"); return "Hello world!"; }
在主线程调用方法:
static void Main1() { MyDelegate dl = DelegateMethod;//给委托定义方法,在例子中,相当于把快递怎么送告诉这个快递员 IAsyncResult ar = dl.BeginInvoke(5000, null, null); //执行委托,执行方法 while (!ar.IsCompleted) { Console.Write("."); Thread.Sleep(50); } string result = dl.EndInvoke(ar);//获取有返回值的委托的返回值 Console.WriteLine("result: {0}", result); }
可以看到,委托线程执行5秒钟,主线程不停的循环判断委托线程是否完成(用IsCompleted属性判断)。如果没完成则断续打点,如果完成则跳出循环。用EndInvoke方法获取委托的返回值。如果方法未执行完,EndInvoke方法就会一直阻塞,直到被调用的方法执行完毕。BeignInvoke方法有3个输入参数,更多用法可以查看前言提到的系列博客,也可以查看MSDN。
自定义事件
之前已经说了,在C#所有自定义事件都是委托,具体实现过程如下:
//自定义事件前,先要定义委托 public delegate void HitSuccess(); public event HitSuccess OnHitSuccess;
这是一个小游戏中使用的一个委托,无输入输出,只执行方法,执行什么方法?不知道,这是要在类外面定义的。这样自定义事件就完成了,触发可以像方法一样调用。
OnHitSuccess();//触发事件
多线程
.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); } } }
后记:Xamarin.Forms使用了大量的委托解决了大量的跨平台兼容性问题,如IOS和Android平台实现同样功能实现的代码不同,只需在Xamarin中建立委托,在各自平台中去实现委托内容即可。除了Delegate委托还大量使用了Action等泛型委托,个人觉得熟练掌握委托是学习Xamarin跨平台项目开发的一个关键。除此之外还同时大量使用了Task代替了ThreadPool。
·END· 虽然有种烂尾的感觉,但是就先写到这了。如有错误,还请指正,我的博客原文地址