代码改变世界

我眼中委托的真正面貌(三)

2009-05-14 12:37  独孤残云  阅读(2253)  评论(20编辑  收藏  举报

最近有些忙,所以这篇随笔写的晚了一些。

 

其实,恐怕大家在看完(一)之后便已经在心中产生了一个呼之欲出的想法——委托就是函数指针!最初,我也曾有过这样的想法(可是,这样观点其实是不正确的),只是并未在相应的文献资料中找到支撑。然而这是后话,是当我自己对于委托的理解到达可以写得出第一篇随笔之后才可能有的见解。因此,先前的几篇虽名为《我眼中委托的真正面貌》,实则是我自己对于委托的一个探索过程,因此,我以委托常见且较有代表性的几个用法来组织文章的脉络。个人感觉,对于一个未知的对象,这样一个由浅入深的过程其实是很有必要的。至少,我希望可以帮那些和我一样对于委托并不是太了解的朋友们少走一些弯路。

 

不过,想法归想法,我说过暂时没有找到相应的理论支撑。因此,我仍然以探索的态度来阐述自己的一些观点,欢迎大家批评指正,同时希望已经完全摸透委托机制的高人们不吝赐教。

 

前不久一个偶然的机会搜到了这样一篇文章:

从函数指针到代理(C#代理入门)

作者是木头象朋友。他在文中为我们阐述了这样的观点:委托是函数指针功能的扩展。

 

其实,个人感觉这句话阐明的观点还是有一定的道理的,感兴趣的朋友们可以支持一下木头象朋友的帖子。这个观点究竟正确与否,我们姑且不论。然而,我们却可以以此为论题,来探讨一下委托比之函数指针究竟有哪些功能上的扩展。

 

C#中要了解一个对象自身的一些特质其实很简单,我们甚至无需依赖于MSDN,你只需声明相应的对象,在此之后点一个“.”,C#编译器的智能感知功能便会自动列出这个对象的相关属性及其方法的一些简单描述。

 

我们用一个实例化的委托对象进行如上操作,便可大致了解委托对象的一些特有的方法和属性了。这里我们讨论几个关键方法的使用方法:

 

9.异步委托的实现

 

using System.Threading;

namespace AsyncDelegate
{
    
class Program
    {
        
//定义委托
        delegate void MyDelegate();

        
static void Main(string[] args)
        {

            
//声明委托对象
            MyDelegate ObjDelegate = new MyDelegate(ObjDelegateFun);

            
//使用异步方式
            IAsyncResult ObjIr = ObjDelegate.BeginInvoke(null,null);
            Console.WriteLine(
"委托方法执行中.");
            Thread.Sleep(
5000);
            ObjDelegate.EndInvoke(ObjIr);
            Console.ReadLine();
        }

        
static private void ObjDelegateFun()

        {
            Thread.Sleep(
5000);

            Console.WriteLine(
"委托方法调用成功!");
        }
    }
}

 

 

所谓异步委托,主要用到了委托对象的BeginInvoke()方法以及EndInvoke()方法。和同步执行的Invoke()方法相比,这两个方法具有如下的特性:

 

BeginInvoke()方法以异步方式开启目标方法,也就是说,其在调用之后会立即返回,即使是执行时间相当长的目标方法对于BeginInvoke()而言也不会带来任何的阻塞。如果是依靠委托来执行耗时相当长的方法,并且我们不急于得到方法的返回值(亦或是操控其他对象的效果)或者我们根本就不用得到方法的返回值,则以这种异步方式来执行无疑是最合适的。

 

EndInvoke()方法是与BeginInvoke()方法配对执行的,用于检索调用结果。相对前者而言,如果异步调用的方法尚未执行完毕,则EndInvoke()将一直遭到阻塞,直至异步方法执行完毕。其传入参数为调用BeginInvoke()时返回的IAsyncResult型对象。

 

有关BeginInvoke()、EndInvoke()方法,以及IAsyncResult接口对象的详细用法everx朋友在他的随笔中进行了较为全面的阐述,在这里为大家推荐一下:

 

异步委托的用法  

我们再来看上述代码。Main()方法的第二句以异步方式执行委托方法,方法则在刚开始执行时被迫暂停5秒,然而,执行时我们会发现"委托方法执行中...."这句话几乎是在程序开始的瞬间就被显示出来了。这说明,BeginInvoke()在执行后是马上返回的,并未遭到任何的阻塞。在这之后,我们将Main()方法同样暂停5秒,而后以EndInvoke()获得委托方法的返回效果:"委托方法调用成功!"。这样,我们实现了委托的异步调用。

 

大家发现了吗?这种异步委托机制和C#中的多线程机制有异曲同工之妙!而事实上,在相应的目标方法中看线程号和主线程确实是不一样的。不过,在查阅了相关的一些资料之后,我还是得知了其中的一些细微的差别。这里推荐给大家一篇来自Kuffy Wang朋友的文章:

异步委托与多线程的区别

 

下面我们使用回调方式来执行异步委托:

 

92)回调式异步委托

 

using System.Threading;

namespace AsyncDelegate
{
    
class Program
    {
        
//定义委托
        delegate void MyDelegate();

        
static void Main(string[] args)
        {
            
//声明委托对象
            MyDelegate ObjDelegate = new MyDelegate(ObjDelegateFun);

            
//使用异步方式
            IAsyncResult ObjIr = ObjDelegate.BeginInvoke(new AsyncCallback(CallbackFun), ObjDelegate);

            Console.WriteLine(
"委托方法执行中.");
            Console.ReadLine();
        }

        
static private void ObjDelegateFun()
        {
            Thread.Sleep(
5000);
            Console.WriteLine(
"委托方法调用成功!");
        }

        
static private void CallbackFun(IAsyncResult ar)
        {
            MyDelegate ObjDelegate 
= (MyDelegate)ar.AsyncState;

            ObjDelegate.EndInvoke(ar);

            Console.WriteLine(
"回调方法执行完毕!");

            Console.ReadLine();
        }
    }
}

 

 

对比以上的两段代码,我们不难发现其中的差别,与上一段代码相比,我们在这里使用了BeginInvoke()的另一个重载方法。其中的一个参数即为BeginInvoke()方法指定的回调方法,在相应的目标方法执行完毕之后将调用该回调方法,并且可以在相应的回调用法中调用结束动作。

 

这里值得顺带一提的是,有关我写上一篇随笔时留下的关于Control.Invoke()方法的疑问。

大家可能会发现,本节中提到的[delegate].Invoke()方法与上节中提到的Control.Invoke()方法在名称书写上是一样的,那么他们在功能上是否存在着些许的相似之处呢?以下是我做出的总结:

 

                                       Return          Work Thread

 

Control.Invoke                完成工作         强制於 UI Thread

Control.BeginInvoke            立即            强制於 UI Thread

[delegate].Invoke              完成工作         Call Invoke Thread

[delegate].BeginInvoke          立即            新的背景 Thread

 

从中大家可以发现一个关键的问题,不论委托对象也好,Control对象也好,他们的Invoke ()方法都是以绝对同步的方式执行,而BeginInvoke()方法则是以绝对异步的方式执行。

 

在这里,我之所以强调“绝对” 两个字,是为了提醒读者虽然“异步”与“多线程”间存在着密切的联系,但异步并不等于在原程序的基础上开设子线程,同步亦并非一定要针对单线程的程序而言。

 

我们不妨再以上一篇随笔中“跨线程操控控件”这段代码来说明问题,当然为说明问题方便,我做了部分调整:

using System.Threading;

namespace MulTrdDelegate
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //定义委托类型
        public delegate void TreadDelgate();

        //初始化子线程对象
        private Thread demoThread = null;

        private void button1_Click(object sender, EventArgs e)
        {
            demoThread = new Thread(new ThreadStart(ThreadProcUnsafe));

            demoThread.Start();
        }

        public void ThreadProcUnsafe()
        {

            if (this.textBox1.InvokeRequired)
            {
                TreadDelgate Objdelegate = new TreadDelgate(SetText);

                this.Invoke(Objdelegate, new object[] {});

         MessageBox.show("委托方法已返回!");
            }

            else

            {

                SetText();

     }
        }
 
        private void SetText()
        {

            Thread.sleep(5000);//人为延长方法的执行时间
            string text = "这个控件的内容是由子线程实现的";

            this.textBox1.Text = text;
        }
    }
}

大家留心我在代码中用红笔勾注的这句代码,如果说委托方法在调用时立即返回,那么这句代码会马上执行。不过,当大家运行时就会发现,这句代码是在SetText()方法返回之后才执行的——这便是Control.Invoke()方法所起到的作用了。大家注意,本身这是一个多线程程序,按理说this.Invoke(Objdelegate, new object[] {});将相应的委托方法强制到主线程去执行,子线程是不受影响的,不过这里却是完全按照单线程的方式在执行的。这就是所谓的“绝对同步”,也就是说即使是多线程也同样会强制按照单线程的方式来执行。这里和本节提到的[delegate].Invoke ()方法执行方式完全一致。

感兴趣的读者也可以尝试将this.Invoke()改为this.BeginInvoke(),查看一下运行效果。没错,和本节提到的[delegate].BeginInvoke ()方法执行方式完全一致。

也就是说,不管当前运行的环境是多线程还是单线程。同步方法会强制程序按照单线程的方式执行,异步方法则强制程序按照多线程方式执行。

其实,从写第一篇关于委托的随笔开始,就不断有园友问我:什么时候采用委托?在这里,我并不打算对这个问题做出正面回答。原因很简单——程序效果是单一的,实现方式则是多种多样的。这里提供本人在学习委托过程中的几个相当有代表性的实例,仅仅是为了帮助读者们以自己的方式来理解委托的用途。每个人有每个人看待事物的独特方式,别人的思想到你身上不一定就行得通。

真心希望以上的几篇随笔可以帮助读者对委托有一个更深入的认识,届时大家自然可以真真正正的总结出适合于自己思路的委托使用环境,以及,你自己眼中所看到的委托的真正面貌