一提到委托,浮如今咱们脑海中的大概是听的最多的就是相似C++的函数指针吧,呵呵,至少个人第一个反应是这样的。数组
关于委托的定义和使用,已经有诸多的人讲解过,而且讲解细致入微,尤为是张子阳的那一篇。我就不用多废话了。异步
今天我要说的是C#中的三种委托方式:Func委托,Action委托,Predicate委托以及这三种委托的常见使用场景。ide
首先来讲明Func委托,经过MSDN咱们能够了解到,Func委托有以下的5种类型:函数
(1) *delegate TResult Func<TResult>();
(2)*delegate TResult Func<T1,TResult>(T1 arg1);
(3) *delegate TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2);
(4)*delegate TResult Func<T1,T2,T3,TResult>(T1 arg1, T2 arg2, T3 arg3);
(5)*delegate TResult Func<T1,T2,T3,T4,TResult>T1 arg1, T2 arg2, T3 arg3, T4 arg4);
其中(1)只能委托无参可是有返回值的函数,TResult就是其返回类型。post
而(2)只能委托具备一个传入参数,有返回值的函数,T1为一个传入参数,TResult为返回类型。this
(3)只能委托具备二个传入参数,有返回值的函数,T1和T2为两个传入参数,TResult为返回类型,(4)和(5)以此类推。spa
那么如何来使用呢? 下面给出一个简单的几个例子:线程
#region Func委托 ///Func<TResult>的用法 ///这里TResult表明函数的返回值类型 ///只能代理返回值为TResult类型的无参函数 Func<string> func = delegate() { return "我是Func<TResult>委托出来的结果"; }; Console.WriteLine(func()); Console.ReadKey(); ///Func<T,TResult>的用法 ///这里的T为代理的函数的传入类型,TResult表明函数的返回值类型 ///只能代理参数为T类型,返回值为TResult类型的函数 Func<string, string> funcOne = delegate(string s) { return s.ToUpper(); }; Console.WriteLine(funcOne("我是Func<T,TResult>委托出来的结果")); Console.ReadKey(); ///Func<T1,T2,TResult>的用法 ///这里T1,T2为代理的函数的传入类型,TResult表明函数的返回值类型 ///只能代理参数为T1,T2类型,返回值为TResult类型的函数 Func<string, string, string> funcTwo = delegate(string value1, string value2) { return value1 + " " + value2; }; Console.WriteLine(funcTwo("我是", "Func<T1,T2,TResult>委托出来的结果")); Console.ReadKey(); #endregion
上面代码中,我用了匿名方法来代替函数,其中delegate()表明无参函数,delegate(string s)表明有一个传入参数的函数,如下的以此类推。代理
而后须要说明的就是Action委托,这个委托也是很是经常使用的,尤为是在涉及到线程和界面交互的时候,配合着lamada表达式使用,很是方便的实现两者的交互。后面我会提到用法。指针
来看看Action委托的几种表现形式:
(1) * delegate void Action(); 无参,无返回值
(2)* delegate void Action<T>(T1 arg1);
(3)* delegate void Action<T1,T2>(T1 arg1, T2 arg2);
(4)* delegate void Action<T1,T2,T3>T1 arg1, T2 arg2, T3 arg3);
(5)* delegate void Action<T1,T2,T3,T4>T1 arg1, T2 arg2, T3 arg3, T4 arg4);
从上面能够看出,总共有5中表现形式,其中(1)既没有传入参数,也没有返回值,那么它适合代理那些无参,无返回值的函数;(2)有一个传入参数,无返回值,适合代理有参,无返回值的函数,(3)(4)(5)以此类推。最都容纳四个传入参数。
那么如何使用呢?下面有一些简单的例子:
#region Action的用法 ///Action<T>的用法 ///这里的T为代理函数的传入类型,无返回值 Action<string[]> action = delegate(string[] x) { var result = from p in x where p.Contains("s") select p; foreach (string s in result.ToList()) { Console.WriteLine(s); } }; string[] str={ "charlies","nancy","alex","jimmy","selina"}; action(str); Console.ReadKey(); #endregion
上面的例子是经过传入的String类型的数组,找出其中包含有字符s的项,而后输出到控制台。
最后一个就是Predicate委托,这个的形式比较少一些,就是一个传入参数,返回值为bool类型,具体示例以下:
#region Action的用法 ///Action<T>的用法 ///这里的T为代理函数的传入类型,无返回值 Action<string[]> action = delegate(string[] x) { var result = from p in x where p.Contains("s") select p; foreach (string s in result.ToList()) { Console.WriteLine(s); } }; string[] str={ "charlies","nancy","alex","jimmy","selina"}; action(str); Console.ReadKey(); #endregion
上面的代码其实也是判断String数组中有没有包含s的项,有的话就在控制台打印出 They contain.没有的话就打印出They don't contain.
总结一下这三个的特色就是:
Func能够接受0个至4个传入参数,必须具备返回值
Action能够接受0个至4个传入参数,无返回值
Predicate只能接受一个传入参数,返回值为bool类型
下面附上所有实现代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace DelegateIntegrateConsoleApp { class Program { static void Main(string[] args) { #region Func委托 ///Func<TResult>的用法 ///这里TResult表明函数的返回值类型 ///只能代理返回值为TResult类型的无参函数 Func<string> func = delegate() { return "我是Func<TResult>委托出来的结果"; }; Console.WriteLine(func()); Console.ReadKey();
///Func<T,TResult>的用法 ///这里的T为代理的函数的传入类型,TResult表明函数的返回值类型 ///只能代理参数为T类型,返回值为TResult类型的函数 Func<string, string> funcOne = delegate(string s) { return s.ToUpper(); }; Console.WriteLine(funcOne("我是Func<T,TResult>委托出来的结果")); Console.ReadKey();
///Func<T1,T2,TResult>的用法 ///这里T1,T2为代理的函数的传入类型,TResult表明函数的返回值类型 ///只能代理参数为T1,T2类型,返回值为TResult类型的函数 Func<string, string, string> funcTwo = delegate(string value1, string value2) { return value1 + " " + value2; }; Console.WriteLine(funcTwo("我是", "Func<T1,T2,TResult>委托出来的结果")); Console.ReadKey();
/*************余下的相似上面的这种操做,最多能够接受四个传入参数*************** *delegate TResult Func<TResult>(); *delegate TResult Func<T1,TResult>(T1 arg1); *delegate TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2); *delegate TResult Func<T1,T2,T3,TResult>(T1 arg1, T2 arg2, T3 arg3); *delegate TResult Func<T1,T2,T3,T4,TResult>T1 arg1, T2 arg2, T3 arg3, T4 arg4); */
#endregion
#region Action的用法 ///Action<T>的用法 ///这里的T为代理函数的传入类型,无返回值 Action<string[]> action = delegate(string[] x) { var result = from p in x where p.Contains("s") select p; foreach (string s in result.ToList()) { Console.WriteLine(s); } }; string[] str={ "charlies","nancy","alex","jimmy","selina"}; action(str); Console.ReadKey();
/***************余下的相似上面的这种操做,最多能够接受四个传入参数********** * delegate void Action(); 无参,无返回值 * delegate void Action<T>(T1 arg1); * delegate void Action<T1,T2>(T1 arg1, T2 arg2); * delegate void Action<T1,T2,T3>T1 arg1, T2 arg2, T3 arg3); * delegate void Action<T1,T2,T3,T4>T1 arg1, T2 arg2, T3 arg3, T4 arg4); */
#endregion
#region Predicate ///bool Predicate<T>的用法 ///输入一个T类型的参数,返回值为bool类型 Predicate<string[]> predicate = delegate(string[] x) { var result = from p in x where p.Contains("s") select p; if (result.ToList().Count > 0) { return true; } else { return false; } }; string[] _value = { "charlies", "nancy", "alex", "jimmy", "selina" }; if (predicate(_value)) { Console.WriteLine("They contain."); } else { Console.WriteLine("They don't contain."); } Console.ReadKey();
#endregion
} } }
下面这部分主要讲解如何在WinForm中利用这些委托进行线程和界面的交互。
首先对于Func来讲,因为其必须具备返回值,因此咱们能够利用以下代码来实现线程和界面的交互:
#region 利用Func实现线程和界面交互 private void AlternationUsingFunc(object text) { //无参数,可是返回值为bool类型 this.Invoke(new Func<bool>(delegate() { button1.Text = text.ToString(); return true; //返回值 })); } private void AlternationUsingFuncThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingFunc); ThreadPool.QueueUserWorkItem(waitCallBack, "Func的使用"); } private void button1_Click(object sender, EventArgs e) { AlternationUsingFuncThread(); } #endregion
其中
this.Invoke(new Func<bool>(delegate() { button1.Text = text.ToString(); return true; //返回值 }));
这段代码中利用了Func<TResult>这种类型,也就是没有传入参数,可是有一个bool类型的返回值,而后将这个函数利用加入到线程池中,最后运行,这里咱们成功的设置了button1的text为“Func的使用”。
而后,对于Action来讲,因为其能够无参,无返回值,那么它的交互方式最为简便,同时也是使用最多的,先看有参的调用方式:
#region 利用Action实现线程和界面交互 private void AlternationUsingAction(object text) { //须要一个T类型的参数,无返回值 this.Invoke(new Action<object>(delegate(object myText) { myText = text; button2.Text = text.ToString(); }),text); } private void AlternationUsingActionThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingAction); ThreadPool.QueueUserWorkItem(waitCallBack,"Action的使用"); } private void button2_Click(object sender, EventArgs e) { AlternationUsingActionThread(); } #endregion
在上面的代码示例中,咱们使用了带有一个传入参数的Action委托,固然了,匿名类型delegate(object myText)匿名代理了具备一个传入参数的函数。
其实简单点来讲,能够像以下方式使用:
this.Invoke((Action)(()=> { button2.Text = text.ToString(); }));
这样就显得很是的方便。
最后一个固然是Predicate委托,和上面相似,只是写起来麻烦一些,它须要一个传入参数,而且返回一个bool类型:
#region 利用Predicate实现线程和界面的交互 private void AlternationUsingPrecidate(object text) { //须要一个T类型的参数,返回bool类型 this.Invoke(new Predicate<object>(delegate(object myText) { myText = text; button3.Text = myText.ToString(); return true; //返回值 }),text); } private void AlternationUsingPrecidateThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingPrecidate); ThreadPool.QueueUserWorkItem(waitCallBack,"Predicate的使用"); } private void button3_Click(object sender, EventArgs e) { AlternationUsingPrecidateThread(); } #endregion
具体的注释我已经写在代码中了,最后运行,能成功的将button3的Text置为“Predicate的使用.”
下面是所有实现代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace DelegateIntegrateWinFormApp { public partial class mainFrm : Form { public mainFrm() { InitializeComponent(); } private void mainFrm_Load(object sender, EventArgs e) { /****************************注意例子中的使用方法**************** * delegate TResult Func<TResult>(); 无参,可是返回值为TResult类型 * delegate void Action<T>(T1 arg1); 有一个参数arg1,可是无返回值 * delegate bool Predicate<T>(T arg); 有一个参数arg,返回bool类型 * **************************************************************/ } #region 利用Func实现线程和界面交互 private void AlternationUsingFunc(object text) { //无参数,可是返回值为bool类型 this.Invoke(new Func<bool>(delegate() { button1.Text = text.ToString(); return true; //返回值 })); } private void AlternationUsingFuncThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingFunc); ThreadPool.QueueUserWorkItem(waitCallBack, "Func的使用"); } private void button1_Click(object sender, EventArgs e) { AlternationUsingFuncThread(); } #endregion #region 利用Action实现线程和界面交互 private void AlternationUsingAction(object text) { //须要一个T类型的参数,无返回值 this.Invoke(new Action<object>(delegate(object myText) { myText = text; button2.Text = text.ToString(); }),text); } private void AlternationUsingActionThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingAction); ThreadPool.QueueUserWorkItem(waitCallBack,"Action的使用"); } private void button2_Click(object sender, EventArgs e) { AlternationUsingActionThread(); } #endregion #region 利用Predicate实现线程和界面的交互 private void AlternationUsingPrecidate(object text) { //须要一个T类型的参数,返回bool类型 this.Invoke(new Predicate<object>(delegate(object myText) { myText = text; button3.Text = myText.ToString(); return true; //返回值 }),text); } private void AlternationUsingPrecidateThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingPrecidate); ThreadPool.QueueUserWorkItem(waitCallBack,"Predicate的使用"); } private void button3_Click(object sender, EventArgs e) { AlternationUsingPrecidateThread(); } #endregion } }
那么,如今对于WPF来讲,该如何来使用呢?其实在WPF中,和winform中相似,只是在WPF中要实现线程和界面的交互,咱们须要用Dispatcher来实现,也就是形如Control.Dispatcher.Invoke()的方式,因为与Winform实现方式无多大差异,这里我就直接附上所有代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Threading; namespace WpfApplication1 { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { /****************************注意例子中的使用方法**************** * delegate TResult Func<TResult>(); 无参,可是返回值为TResult类型 * delegate void Action(); 无参,无返回值 * delegate bool Predicate<T>(T arg); 有一个参数arg,返回bool类型 * 须要注意,与WinForm中不一样的是,WPF中须要利用Control.Dispatcher.Invoke来实现,其余相似. * **************************************************************/ } #region 利用Func实现线程和界面交互 private void AlternationUsingFunc(object text) { //无参数,可是返回值为bool类型 button1.Dispatcher.Invoke(new Func<bool>(delegate() { button1.Content = text.ToString(); return true; //返回值 })); } private void AlternationUsingFuncThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingFunc); ThreadPool.QueueUserWorkItem(waitCallBack, "Func的使用"); } private void button1_Click(object sender, RoutedEventArgs e) { AlternationUsingFuncThread(); } #endregion #region 利用Action实现线程和界面交互 private void AlternationUsingAction(object text) { //无参数,无返回值 //button2.Dispatcher.Invoke(new Action(delegate() //{ // button2.Content = text.ToString(); //})); //或者 button2.Dispatcher.Invoke((Action)(()=> { button2.Content = text.ToString(); })); } private void AlternationUsingActionThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingAction); ThreadPool.QueueUserWorkItem(waitCallBack, "Action的使用"); } private void button2_Click(object sender, RoutedEventArgs e) { AlternationUsingActionThread(); } #endregion #region 利用Predicate实现线程和界面的交互 private void AlternationUsingPrecidate(object text) { //须要一个T类型的参数,返回bool类型 this.button3.Dispatcher.Invoke(new Predicate<object>(delegate(object myText) { myText = text; button3.Content = myText.ToString(); return true; //返回值 }), text); } private void AlternationUsingPrecidateThread() { WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingPrecidate); ThreadPool.QueueUserWorkItem(waitCallBack, "Predicate的使用"); } private void button3_Click(object sender, RoutedEventArgs e) { AlternationUsingPrecidateThread(); } #endregion } }
逐个点击界面上的按钮,咱们能够看到成功实现了线程和UI的交互:
固然,上面咱们只是说到了在WinForm中和WPF中如何来使用的状况,代码比较简单,也没有具体的应用场景,下面咱们将结合中WPF来模拟一个具体的应用场景:
如今假设我有一个txt文档,名称为newEXO.txt,里面大概有5w行记录,文件大小为30MB左右;同时我手边还有一个oldEXO.txt里面也有5w数据,可是其中有一些记录和newEXO.txt中的不一样,我如今须要对比两个txt文档,找出不一样的记录,并对不一样的记录进行上色操做。
那么如今这里很明确了,咱们须要两个函数,一个是读取记录的函数ChangeText(),一个是上色的函数ChangeColor()。
可是在实际操做中,发现若是我直接利用wpf读取数据的话,逐行读取,耗时10s左右,也就是用户界面会被阻塞10s,而后才会显示给用户,这个体验性是至关很差的,因此拟采用异步方式来导入数据。
同时,考虑到差别比较也会比较耗时,因此也准备采用异步方式来进行对比。
那么问题来了,两个均采用异步方式进行,不免会发生数据未导入完成就开始进行差别比较的可能,因此这里还涉及到一个线程同步的问题。
如今,这里有三个操做,异步的数据导入,异步的差别比较并上色,线程同步。
首先咱们看异步导入,你也能够本身实现一个异步类,经过委托的BeginInvoke方法和EndInvoke方法来实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ThreadSynchorous { public class AsyncInvoke { public void BeginAsync(Func<bool> MyFunction) { Func<bool> func = new Func<bool>(MyFunction); IAsyncResult iar = func.BeginInvoke(new AsyncCallback(EndAsync), func); } public void EndAsync(IAsyncResult iar) { Func<bool> func = (Func<bool>)iar.AsyncState; func.EndInvoke(iar); } } }
因为Action委托的使用方式最为便捷,这里我采用Action委托方式来进行,固然了,:
private void ChangeText() { this.button1.Dispatcher.Invoke((Action)(()=> { string filename = @"C:\newEXO.txt"; using (StreamReader sr = new StreamReader(filename, Encoding.Default)) { string result; while ((result = sr.ReadLine()) != null) { //here perform action } } //label1.Dispatcher.Invoke((new Action(delegate() label1.Dispatcher.Invoke((Action)(()=> { label1.Content += "Loading finish!(Thread.CurrentThreadName=" + Thread.CurrentThread.ManagedThreadId.ToString() + ") "; })); })); }
首先是当点击button1按钮的时候,就启动ChangeText()函数,也即数据导入函数,而后label1会在加载完毕的时候,给出提示信息。
下面再看看ChangeColor()函数:
private void ChangeColor() { this.button1.Dispatcher.Invoke((Action)(()=> { this.button1.Background = Brushes.Red; //here perform large amount of data action and color the result label1.Dispatcher.Invoke((Action)(()=> { label1.Content += "Coloring finish!(Thread.CurrentThreadName=" + Thread.CurrentThread.ManagedThreadId + ") "; })); })); }
能够看到也是当button1点击的时候,会触发ChangeColor函数。因为两者操做比较耗时,为了防止用户界面阻塞,咱们放到线程池中:
ThreadPool.QueueUserWorkItem(o => ChangeText());
ThreadPool.QueueUserWorkItem(o => ChangeColor());
从上面能够看出,当点击按钮button1的时候,两个函数同时被引起,也就是点击按钮的时候进行以下操做:
private void button1_Click(object sender, RoutedEventArgs e) { ThreadPool.QueueUserWorkItem(o => ChangeText()); ThreadPool.QueueUserWorkItem(o => ChangeColor()); label1.Content += " \r\n-------------------------\r\n"; }
看到了什么?
看到了线程运行的混乱,咱们本想让数据先加载,而后比较得出差别着色,惋惜上面的结果中却与想象中的相差甚远.
这里的ChangeText()函数和ChangeColor()函数确定不会像想象的那样顺序执行,那么代码就有问题了,因此为了不这个问题,咱们必须进行线程同步,如何来进行呢? 方法不少,这里我采用EventWaitHandle方式来进行。
EventWaitHandle的Reset方式用来重置信号量,告诉其余运行的进程,大家须要被阻塞;Set方式用来释放信号量,告诉其余运行的进程,大家的阻塞已经被解除,能够继续运行了。
可是其余进行经过什么来知道本身是否能够解除阻塞状态呢? 那就是利用WaitOne方式来判断:
也就是按照以下的代码模式来:
EventWaitHandle waitMeHandle = new EventWaitHandle(false,EventResetMode.ManualReset); private void ChangeText() { waitMeHandle.Reset(); //即将进入下列执行过程,其余须要阻塞 //.... waitMeHandle.Set(); //释放 } private void ChangeColor() { waitMeHandle.WaitOne(); //等待,直到接收到Set信号,才能运行 // ... }
固然上面我举出的例子只是一个Sample,我写过这个软件,利用的是BeginInvoke和EndInvoke方式实现的异步调用,有兴趣能够参见个人这篇文章中提到的软件:
下面是软件截图:
另外在写这篇文章的时候,我在StackOverFlow上面有过提问,就是关于当前的Thread的ThreadId为何一致的问题, 应该说两个函数放到了ThreadPool中,结果出来的ThreadId应该不同才对呀.
其实,正确的答案是个人打印出ThreadId的信息都放在了label1.Dispatcher.Invoke这句话中,而这个Lable1属于界面UI,也就是前台线程,因此ThreadId会是同样的,若是你直接在进入函数的时候,输出ThreadId,你就会发现两个函数运行在不一样的线程上了.
本文中涉及到的源码,能够从这里下载