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

本文介绍了C#中的委托概念及其在异步调用中的应用。详细讲解了如何定义和使用委托,以及如何通过BeginInvoke和EndInvoke实现方法的异步调用。

      这是我在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#中看起来像一个普通的函数调用。

ContractedBlock.gif ExpandedBlockStart.gif Code
class Foo
{
   
void PrintPair(int a, int b)
   {
     Console.WriteLine(
"a = {0}", a);
     Console.WriteLine(
"b = {0}", b);
   }

   
void CreateAndInvoke()
   {
     
// implied 'new MyDelegate(this.PrintPair)':
     MyDelegate del = PrintPair;
     del(
1020);
  }

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

ContractedBlock.gif ExpandedBlockStart.gif Code
struct MyDelegate :  System.MulticastDelegate
{
  
public MyDelegate(object target, IntPtr, methodPtr);

  
private object target;
  
private IntPtr methodPtr;

  
public internal void Invoke(int x, int y)
  
public internal System.IAsyncResult BeginInvoke(int x, int y, 
                  System.IAsyncCallback callback, 
object state);
  
public internal void EndInvoke(System.IAsyncResult  result);

}

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

public   delegate   int  StringDelegate( string  str);


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

ContractedBlock.gif ExpandedBlockStart.gif Code
public sealed class StringDelegate : System.MulticastDelegate
ExpandedBlockStart.gifContractedBlock.gif
{
   
public StringDelegate (object obj, int method);
   
public virtual int Invoke(string str);
   
public virtual IAsyncResult BeginInvoke(string str, 
      AsyncCallback asc, 
object stateObject);
   
public virtual int EndInvoke(IAsyncResult result);
}

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

ContractedBlock.gif ExpandedBlockStart.gif Code
using System;
ExpandedBlockStart.gifContractedBlock.gif
public static class App {
   
public delegate void MyDelegate(int x, int y);
   
public static void PrintPair(int a, int b)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
{
     Console.WriteLine(
"a = {0}", a);
     Console.WriteLine(
"b = {0}", b);
   }

   
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
{
     
// Implied 'new MyDelegate(this.PrintPair)':
     MyDelegate d = PrintPair;
     
// Implied 'Invoke':
     d(1020);
   }

}

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

=   10
=   20

内部委托

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

delegate   string  MyDelegate( int  x);

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

private   sealed   class  MyDelegate : MulticastDelegate
ExpandedBlockStart.gifContractedBlock.gif
{
    
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
ExpandedBlockStart.gifContractedBlock.gif
{
  
public string MyFunc(int foo)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
{
     
return "MyFunc called with the value  '" + foo + "' foo foo;
  }

}

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

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

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

ContractedBlock.gif ExpandedBlockStart.gif Code
using System;
delegate string MyDelegate(int x);
class MyType
ExpandedBlockStart.gifContractedBlock.gif
{
  
public string MyFunc(int foo)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
{
     
return "MyFunc called with the value '" + foo + "' for foo";
  }

}


ExpandedBlockStart.gifContractedBlock.gif
public class Program {
  
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
{
    MyType mt 
= new MyType();
    MyDelegate md 
= mt.MyFunc;
    Console.WriteLine(md.Invoke(
5));
    Console.WriteLine(md(
5));
  }

}

代码编译后输出结果:

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方法进行了异步调用:

ContractedBlock.gif ExpandedBlockStart.gif Code
using System;
using System.Threading;
ExpandedBlockStart.gifContractedBlock.gif
public class Program {
 
public delegate int TheDelegate( int x, int y);
ExpandedSubBlockStart.gifContractedSubBlock.gif 
static int ShowSum( int x, int y ) {
 
int sum = x + y;
 Console.WriteLine(
"Thread #{0}: ShowSum()  Sum = {1}"
                   Thread.CurrentThread.ManagedThreadId, sum);
 
return sum;
}

ExpandedSubBlockStart.gifContractedSubBlock.gif
public static void Main() {
  TheDelegate d 
= ShowSum;
  IAsyncResult ar 
= d.BeginInvoke(1010nullnull);
  
int sum = d.EndInvoke(ar);
  Console.WriteLine(
"Thread #{0}: Main()     Sum = {1}"
                    Thread.CurrentThread.ManagedThreadId, sum);
  }

}

输出:

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

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

public   interface  IAsyncResult
ExpandedBlockStart.gifContractedBlock.gif
{
    
// Properties
ExpandedSubBlockStart.gifContractedSubBlock.gif
    object AsyncState get; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    WaitHandle AsyncWaitHandle 
get; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    
bool CompletedSynchronously get; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    
bool IsCompleted get; }
}

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

ContractedBlock.gif ExpandedBlockStart.gif Code
using System;
ExpandedBlockStart.gifContractedBlock.gif
public sealed class Program {
  
delegate int IntIntDelegate(int x);
ExpandedSubBlockStart.gifContractedSubBlock.gif  
private static int Square(int x) return x * x; }
  
private static void AsyncDelegateCallback(IAsyncResult ar)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
{
    IntIntDelegate f 
= (IntIntDelegate)ar.AsyncState;
    Console.WriteLine(f.EndInvoke(ar));
  }


  
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
{
    IntIntDelegate f 
= Square;

ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//* Version 1: Spin wait (quick delegate method) */
    IAsyncResult ar1 
= f.BeginInvoke(10nullnull);
    
while (!ar1.IsCompleted)
        
// Do some expensive work while it executes.
    Console.WriteLine(f.EndInvoke(ar1));

ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//* Version 2: WaitHandle wait (longer delegate method) */
    IAsyncResult ar2 
= f.BeginInvoke(20nullnull);
    
// Do some work.
    ar2.AsyncWaitHandle.WaitOne();
    Console.WriteLine(f.EndInvoke(ar2));

ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//* Version 3: Callback approach */
    IAsyncResult ar3 
= f.BeginInvoke(30, AsyncDelegateCallback, f);
    
// We return from the method (while the delegate executes).
  }

}

输出:

100
400

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

ContractedBlock.gif ExpandedBlockStart.gif Code
using System;
class App
ExpandedBlockStart.gifContractedBlock.gif
{
   
delegate void GetData(byte[] b);
   
static void GetBuf(byte[] b)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
{
      
for (byte x = 0; x < b.Length; x++)
         b[x] 
= (byte)(x*x);
   }

   
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
{
      GetData d 
= new GetData(App.GetBuf);
      
byte[] b = new byte[10];
      IAsyncResult ar;
      ar 
= d.BeginInvoke(b, nullnull);
      ar.AsyncWaitHandle.WaitOne();
      
for (int x = 0; x < b.Length; x++)
         Console.Write(
"{0} ", b[x]);
   }

}

输出:

0
1
4
9
16
25
36
49
64
81
100

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

ContractedBlock.gif ExpandedBlockStart.gif Code
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;
ExpandedBlockStart.gifContractedBlock.gif
class Program {
  
public delegate int MyDelegate(int x, int y);
  
static AutoResetEvent e = new AutoResetEvent(false);
ExpandedSubBlockStart.gifContractedSubBlock.gif  
static int WriteSum( int x, int y) {
  Console.WriteLine(
"Thread# {0}: Sum = {1}"
          Thread.CurrentThread.ManagedThreadId, x 
+ y);
  
return x + y;
}


ExpandedSubBlockStart.gifContractedSubBlock.gif
static void SumDone(IAsyncResult async) {
  Thread.Sleep( 
1000 );
  
// AsyncResult of the System.Runtime.Remoting.Messaging namepsace
  MyDelegate func = ((AsyncResult) async).AsyncDelegate as MyDelegate;
  
int sum = func.EndInvoke(async);
  Console.WriteLine(
"Thread# {0}: Callback method sum = {1}"
                    Thread.CurrentThread.ManagedThreadId, sum);
  e.Set();
}


ExpandedSubBlockStart.gifContractedSubBlock.gif
static void Main() {
  MyDelegate func 
= WriteSum;

  
// the C# 2.0 compiler infer a delegate object of type
  
// AsyncCallback to reference the SumDone() method
  IAsyncResult async = func.BeginInvoke(1010, SumDone, null);
  Console.WriteLine(
"Thread# {0}: BeginInvoke() called! Wait for SumDone() completion."
                    Thread.CurrentThread.ManagedThreadId);
  e.WaitOne();
  Console.WriteLine(
"Thread# {0}: Bye."
                    Thread.CurrentThread.ManagedThreadId);
  }

}

编译执行后的结果如下:

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



转载于:https://www.cnblogs.com/kaihua/archive/2009/08/30/Asynchronous_Delegates.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值