.NET Framework中的委托:了解异步委托(我的第一篇博文)

      这是我在cnblogs的第一篇博客。在大学专业是应用物理,就学过C语言,工作后,要用C#编程,狂学了两个礼拜,就开始跟着做项目了。很多地方都不懂,基本是现学现卖。以后打算重点加强一下C#的学习,总觉得光看别人的代码,长进的不快,自己目前又写不出什么原创的文章,就决定先从翻译做起,每个礼拜,在http://www.codeproject.com/ 上面找一篇C#相关文章,翻译一遍,贴到博客上,以后水平有所提高,有自己的想法之后,再写一些原创的文章。没做过翻译,开始的时候,肯定会有些地方翻译的不到位甚至错误,还请大家多多指教。

       原文地址 http://www.codeproject.com/KB/cs/Method_Invocation.aspx

介绍

       委托是.NET框架支持的一种特殊类型。它可以被实例化,并且可以通过任何与方法签名相匹配的目标方法与实例组合形成。C#允许使用 delegate 关键字来创建特殊类,我们称这个特殊类为委托类。这些委托类的实例则被称为委托对象。从概念上来讲,一个委托对象是一个或者多个方法(静态或实例)的引用。因此我们可以像调用一个方法那样,来调用一个委托。这就导致对一个方法的调用,但是需要注意的是,对这些方法的调用是通过调用委托对象的同一个线程来完成的。我们把这个作为一个同步调用。但是,在对一个方法进行同步调用时,调用线程会在调用活动时阻塞。当线程被阻塞时,它可以创建其他的线程,事实上,这个时候,CPU仍有可能处于空闲。因此,新创建的这些线程尽管有可能没有占用CPU,但是他们这是在浪费资源,这是不经济的。当一个线程对一个方法做异步调用时,这个调用能够立即返回。调用线程不会被阻塞,它可以继续做其他的工作。.NET底层结构为这个方法调用生成一个线程并且通过调用代码传递参数。这个异步线程可以在调用线程进行的同时,运行该方法。如果这个方法产生了一些数据并且返回了这些值,那么调用线程必须能够访问这些数据。.NET异步特征支持两个机制:调用线程要么要求得到结果,要么.NET底层结构在结果准备就绪后,将结果传递给调用线程。这篇文章的目的就是解释委托的概念并且讲解如何异步的使用委托。
委托
     在C#中,一个新的委托类型通过delegate关键字来创建。

public delegate void MyDelegate(int x, int y);

我们新建了一个委托类型,名为MyDelegate,它可以通过拥有两int型参数、返回类型为void的方法来构建。我们的委托可以通过一个目标形成、传递并在将来适当的时候被调用。这种调用在C#中看起来像一个普通的函数调用。

Code

还不明白吗?CreateAndInvoke方法构建了一个新的MyDelegate委托,通过带有当前this指针的PrintPair方法作为目标来形成。在优先类型系统中,由编译器产生的实际的空闲显示了一些委托的复杂性。

Code

 这构造器用来通过一个目标对象和一个函数指针形成一个委托。Invoke, BeginInvoke, 和 EndInvoke 方法执行委托调用程序并且作为内敛函数来通知CLR它所提供的执行过程,他们的空闲部分被留空。当BeginInvoke和EndInvoke函数是异步程序模式时,调用就是同步调用。但是,首先要注意的是,MyDelegate类型打破了这个规则,即它的结构只能从System.ValueType来获得,而不能从其他类型获得。委托在通用类型系统(CTS)中有独特的支持,因此,这是被允许的。同时也要注意MyDelegate是从MulticastDelegate获得的,这个类型是C#中所有委托的通用基类,并且它支持多对象的委托。下面来看另一中委托的创建:

public delegate int StringDelegate(string str);


它可以在一个类或全局范围内声明。C#编译器会从这个声明中生成一个新类,这个类派生自System.MulticastDelegate。再次检测这个类和其基类System.Delegate 的方法:

Code

现在,让我们来看一些和我们创建的第一个委托MyDelegate相关代码:

Code

当编译的时候,我们得到这样的输出结果:

= 10
= 20

内部委托

假设我们在C#中再次定义了我们自己的MyDelegate类型,就像这样:

delegate string MyDelegate(int x);

我们现在知道了这是一个函数指针类型,它可以引用任何有一个int类型参数并且返回一个string类型的方法。我们也知道当使用这种委托类型的实例时,我们会声明MyDelegate类型的变量。还有,我们知道在后台,编译器为我们生成了一个新的类类型:

private sealed class MyDelegate : MulticastDelegate
{
    
public extern MyDelegate(object object, IntPtr method);
    
public extern virtual string Invoke(int x);
    
public extern virtual IAsyncResult BeginInvoke(int x, 
                  AsyncCallback  callback, 
object   object);
    
public extern virtual string Endinvoke((IAsyncResult result);
}

现在我们假设我们有自己的自定义类型MyType,带有一个和MyDelegate的方法特征完全匹配的MyFunc方法。注意,现在的参数并没有统一命名。这样是可行的,那是因为委托只要求期待的类型在正确的特征位置被找到:

class MyType
{
  
public string MyFunc(int foo)
  
{
     
return "MyFunc called with the value  '" + foo + "' foo foo;
  }

}

在一个元数据中,一旦我们有了一个委托类型和一个我们想要调用的目标函数,我们必须从目标形成一个实例。通过使用MyDelegate(object, IntPtr)构造器,就构建了一个委托类型的新实例。这段代码将目标作为第一个参数,将一个代码函数的指针作为第二个参数进行传递。语法如下:

MyType mt = new MyType();
MyDelegate md 
= mt.MyFunc;

那么,我们把这些部分累加起来形成一个整体:

Code

代码编译后输出结果:

MyFunc called with the value '5' for foo
MyFunc called with the value 
'5' for foo

那么是是异步委托呢?
在使用一个异步委托之前,记住所有的委托类型都自动地提供名为BeginInvoke和EndInvoke两个方法。这些方法的特性是基于包含他们的委托类型的。例如下面的委托类型:

delegate int MyDelegate(int x, int y)

下面是编译器生成的方法:

IAsyncResult BeginInvoke(int x, int y, AsyncCallback callback, 
             
object object, IAsyncResult result);
int EndInvoke (IAsyncResult result);

这两个方法由编译器生成。以异步方式调用一个方法,你必须用一个拥有同样特性的委托对象引用它。然后,你必须在这个委托对象上调用BeginInvoke方法。正如你看到的,编译器确保BeginInvoke方法的第一个参数是被调用方法的参数。而后两个参数,IAsyncResult和object,稍后再做讨论。异步调用的返回值可以通过EndInvoke方法回收。编译器同时也会确保EndInvoke返回值类型和委托返回类型一致(在我们的例子中,这个类型是int)。EndInvoke调用被阻塞,意味着这个调用只有当异步执行完成才会返回。下面的例子对ShowSum方法进行了异步调用:

Code

输出:

Thread #3: ShowSum()  Sum = 20
Thread #
1: Main()     Sum = 20

BeginInvoke方法对委托的每个参数都有一个参数(就像是调用),并且加了两个参数:一个是IAsyncCallback委托,它在异步操作完成时被调用,另一个参数是object,它作为回调函数的IAsyncResult.AsyncState的属性值被传递。这个方法返回一个IAsyncResult,它可以被用在监测完成,也可以等待在WaitHandel,或者完成异步调用。

public interface IAsyncResult
{
    
// Properties
    object AsyncState get; }
    WaitHandle AsyncWaitHandle 
get; }
    
bool CompletedSynchronously get; }
    
bool IsCompleted get; }
}

当委托完成执行时,必须在这个委托上调用EndInvoke方法,在IAsyncResult中传递。这会清除WaitHandle(如果它被分配了),如果委托执行失败了,就会抛出异常并且有一个和基本方法类型相匹配的返回类型。它把委托调用返回的值返回:

Code

输出:

100
400

当委托完成执行时,必须在这个委托上调用EndInvoke方法,在IAsyncResult中传递。这会清除WaitHandle(如果它被分配了),如果委托执行失败了,就会抛出异常并且有一个和基本方法类型相匹配的返回类型。它把委托调用返回的值返回。现在,如果这个方法有引用类型并且是作为参数传递的参数,那么这个异步方法就会在可以改变它的状态的引用类型上调用方法。可以利用这一点来从异步方法来返回值。下面的例子就说明了这一点。GetData这个委托把System.Array类型的一个对象作为参数。一旦它通过引用被传递进去,这个方法就能改变数组里的值。然而,这个对象如果可以被两个线程访问,必须保证这个共享对象在异步方法完成之前不被调用。

Code

输出:

0
1
4
9
16
25
36
49
64
81
100

使用回调方法,必须用一个作为下一个BeginInvoke方法的最后一个参数的System.AsyncCallback类型的委托对象来引用它。这个方法必须和委托类型保持一致,也就是说它的返回类型必须是void(在下面的例子中)并采用单一的IAsyncResult类型参数:

Code

编译执行后的结果如下:

Thread# 1: BeginInvoke() called! Wait for SumDone() completion.
Thread# 
3: Sum = 20
Thread# 
3: Callback method sum = 20
Thread# 
1: Bye.



posted on 2009-08-30 21:11  火柴没帽  阅读(557)  评论(0编辑  收藏  举报

导航