Programming .Net component 2nd (部分中文翻译2)

第五章  版本控制
       VS 2005中的程序集对话框仅仅是程序集属性集合的可视化编辑工具,我们可以直接修改AssemblyInfo.cs中的值。
       程序集版本包4个部分,一般的,在设置程序集版本时我们必须遵守如下约定:
       major version number: 
       minor version number: 
       Build number: a newer version of the same compatible assembly
       Revision number: some minor change (perhaps a minor bug fix) or changes made due to localization.
       VS 2005默认产生的版本号:1.0.0.0,但命令行产生的是:0.0.0.0。我们可以通过Assembly类的GetName方法返回一个AssemblyName类对象实例。AssemblyName类对象实例包含有一个Fields:Version。
例:修改程序集版本
       Assembly assembly = Assembly.GetExecutingAssembly(  );
       Version version = new Version(1,2,3,4);
       assembly.GetName(  ).Version = version;
其中Version类定义如:
       public sealed class Version : ICloneable,IComparable,
       IComparable<Version>
       {  public Version(  );
              public Version(int major, int minor);
 
             public Version(int major, int minor, int build);
 
             public Version(int major,int minor,int build,int revision);
 
             public Version(string version);
  
            public int Build{get;}
  
            public int Major{get;}
 
             public int Minor{get;}
 
             public int Revision{get;}
     }

.NET程序集分配模型
       私有:在有对程序集的引用时,编译器会将其拷贝到应用程序所在的文件夹中。我们也可以直接将需要的程序集放在同一个文件夹中。这种分配机制并不能阻止版本问题的发生,故新的版本要求是向后兼容的。
       共享:共享程序集是可以供多个客户应用程序使用的程序集,我们必须将共享程序集安装在GAC中。
       GAC支持共享程序集的多个版本,同一共享程序集不同版本间可按side-by-side执行。也就是说,如果程序集的旧版本仍然存在,新版本程序集不要求向后兼容。
Assembly resolver: 负责管理程序集兼容并确定客户应用程序总是取得兼容程序集的,NET实体。
       当客户应用程序声明一类其程序集还没有载入,.NET会查询客户端程序集的清单,以确定客户端期望的服务程序集精确命名、版本,然后将这些查序结果传递给Assembly Resolver。Assembly Resolver总是尝试给客户端程序集一个兼容的服务程序集。Assembly Resolver首先查找GAC,如果找到就将其载入客户端;如果没有找到兼容版本,Assembly Resolver继续在客户应用程序的文件夹中查找兼容的私有程序集,如果没找到,就抛出异常。
默认的.NET并不允许客户应用程序同一不兼容的程序集交互。

强命名程序集
       在两中情况下下我们必须使用共享程序集:一、程序集不同版本间执行side-by-side原则;二、多个应用程序共同使用。
       强命名程序集可以被任何程序集引用,但其本身只能引用强命名程序集。
密钥文件不能是共有的,在VS 2005中,即便你使用现存的密钥文件,也是其拷贝。

Friend Assemblies and Strong Names
       一个无强命名的程序集只能指派为同样无强命名程序集的Friend Assembly。
       [assembly: InternalsVisibleTo("MyClient")]
也即如果本程序集无强命名,则客户程序集MyClient必须同样无强命名;然而如果本程序集幽强命名,MyClient不一定必须是强命名的。也就是说,此时我们访问程序集的所有内部方法/成员。这可能导致内部成员暴露的安全问题,我们通过如下可以解决这个问题:
       [assembly:InternalsVisibleTo("MyClient,PublicKeyToken=…..")]

Visual Studio 2005 and Versioning
       VS 2005默认下,添加对GAC程序集的引用实际上是拷贝程序集到应用程序文件夹中。我们可以通过手工移除应用程序文件夹中的私有程序集以使用共享程序集。另外我们还可以通过VS 2005修改引用的属性CopyLocal为false。
       VS 2005默认下,也不识别特有的版本。也即是说,起初程序集有为一低版本,在更新时我们可以直接将高版本的程序集覆盖低版本的。默认下,VS 2005并不对版本有特别的需求(在VS 2005环境下,你用新版本覆盖原来的版本时,VS 2005会重定向引用程序集版本)。我们可以通过VS 2005 引用属性Specific Version为true。

自定义版本策略
       .NET允许管理员的自定义版本策略覆盖缺省assembly-resolving版本策略,并能够将自定义版本策略应用在单个应用程序上,也能将其运用在整个机器范围内。
       管理员可以因任何原因而自定义版本策略,但一般的都是在程序集提供者发布的新版本向后兼容时。.NET缺省的版本邦定机制和resolving策略都会要求精确的版本(即JIT编译时)。也就是说,即便存在向后兼容的新版本,载入的也会是旧的版本;如果新版本覆盖了强命名的私有旧版本时,会抛出异常。为解决这个问题.NET提供了版本的重定向机制。

Custom version-binding policies(.NET Configuration tool)
Administrators can specify a redirection from a particular version to another particular version (such as from 2.0.0.0 to 2.1.0.0), or they can specify a range of redirections (such as from 1.1.0.0-1.3.0.0 to 2.1.0.0).
Redirections can be made from any version to any other version, including from newer versions to older versions

Custom codebase policies(.NET Configuration tool)
The Codebases tab on the assembly properties page lets administrators dictate where the assembly resolver should look for particular versions of the assembly.

Application configuration file
When you provide a custom policy for an application, the configurations you make with the .NET Configuration tool are saved in a special configuration file in the application folder. That file is called <application name>.config (e.g., MyClient.exe.config).

Global Custom Policies
Specifying such custom policies is done exactly the same way as specifying applications custom policies.Global custom policies affect all applications on the machine. Gglobal custom policies are applied downstream, meaning that the application applies its custom policies first, and then global custom policies are applied.

CLR Side-by-Side Execution
Even though the CLR and the various .NET application frameworks consist of many assemblies, all are treated as a single versioning unit. Multiple versions of these units can coexist on any given machine. This is called CLR side-by-side execution.

Version Unification
All .NET applications are hosted in an unmanaged process, which loads the CLR DLLs. That unmanaged process can use exactly one version of each CLR assembly.
The CLR and its application frameworks assemblies are treated as a single versioning unit.
The CLR and the .NET application frameworks aren't designed for mixing and matching.
It's up to the EXE used to launch the process to select the CLR and application frameworks version.

Specifying supported CLR versions
<?xml version="1.0"?>
    <configuration>
       <startup>
          <supportedRuntime version="v2.0.5500.0"/>
          <supportedRuntime version="v1.1.5000.0"/>
       </startup>  
</configuration>
若要改变.NET默认的CLR版本,我们可以通过修改应用程序配置文件显式修改CLR版本策略。

第六章  事件
       在面向组件程序中,服务对象提供给客户端服务是通过客户端通过调用对象的方法和设置其属性来实现的。但如果服务对象的事件要通告给客户端,我们又是什么实现的呢?

Delegate-Based Events
       Publisher:发布事件的对象;
       Sink/Subscriber: 任何对事件有兴趣的部分。
       事件通知器是以Publisher调用Sink方法的形式工作的。
委托是一类型安全的方法引用,它允许我们将调用方法这一行为委托给其他对象。
publisher.SomeMethod = new NumberChangedEventHandler
(subscriber1.OnSomeMethod);
publisher.SomeMethod()
问:我们实际调用的是?又将其委托给谁?
事件发布者拥有一个委托类型的变量,而事件接受器则必须实现一个方法其方法签名同委托相同。

Delegate Inference
    C#中:
publisher.SomeMethod += subscriber1.OnSomeMethod;
在向SomeMethod中添加/移除移目标方法是我们不用实例化一个delegate类型变量而是直接将目标方法名赋给发布者的delegate类型成员。
    同样如果一对象方法包含一个delegate类型的参数,我们也不用实例化一delegate类型实例:
obj.InvokeDelegate(obj.OnSomeMethod);
obj.OnSomeMethod是做为一委托类型变量传入的。
当我们向一个委托类型赋值一方法名时,编译器首先假设为一委托类型,然后验证是否存在一方法其方法签名同该委托类型是否相匹配,最后编译器创建一该委托类型对象实例并将传入的方法名封装在该委托变量中。

泛类/委托
在委托中使用泛类时:
public delegate void GenericEventHandler(T t);
我们在实例化时必须指定具体类型。又如:
public class MyClass<T>
    {
       public delegate void GenericEventHandler(T t);
       public void SomeMethod(T t) {...}
    }
我们有:
MyClass<int>.GenericEventHandler del;
此时del的具体参数类型也为int。当我们为其赋目标方法时,也只能时具体参数为int类的。
    MyClass<int> obj = new MyClass<int>(  );
    del = obj.SomeMethod;
其中“del = obj.SomeMethod;”实际上等效为:
del = new MyClass<int>.GenericEventHandler(obj.SomeMethod);

The event Keyword
由于任何部分都应该可以添加事件接收者,所以事件的发布者必须将其委托声明为一公共成员。然而这也就衍生了一个问题:不论发布者是否有事件发生,我们都可以从其它代码中发布事件(Fire event)。
使用event关键字定义的委托,不论是否声明为public,都只有在发布类中才可以发布事件。其他部分若要发布事件,也只有在发布类定义了公共的发布方法是才可能。
例:未使用event前,我们有:
Publisher.EventOccurried(number);
若我们使用了该关键字:则在客户代码中必须调用:
         Publisher.FireEvent(number)
其中,FireEvent为Publisher类定义的发布事件的公共方法,它封装了EventOccurried。

Defining Delegate Signatures
Event Delegate有一下几个特性:
使用void返回值(暗示不能使用ref/out参数);
一些事件接受者要知道事件的发布者;
事件传入参数,一般继承自(EventArgs)。
例:
public delegate void NumberChangedEventHandler
 (object sender,EventArgs eventArgs);
    public class NumberChangedEventArgs : EventArgs
{ public int Number;//This should really be a property }

    public class MyPublisher
{
 public event NumberChangedEventHandler NumberChanged;
       public void FireNewNumberEvent(EventArgs eventArgs)
       {  //Always check delegate for null before invoking
          if(NumberChanged != null)
          //传入this,以声明是谁发布的。eventArgs参数接受任何EventArgs
          //及其派生类对象实例。
             NumberChanged(this,eventArgs);
       }
    }
this参数让我们记录了谁发布了事件,而EventArgs类型的参数让事件发布者不必关注参数的实际类型。所以我们有上面例子的接受者:
public class MySubscriber
     {
       public void OnNumberChanged(object sender,EventArgs eventArgs)
       {
          NumberChangedEventArgs numberArg;
          numberArg = eventArgs as NumberChangedEventArgs; //转换类型
          Debug.Assert(numberArg != null);
          string message = numberArg.Number.ToString();
          MessageBox.Show("The new number is "+ message);
       }
    }
在客户代码中,我们有:
publisher.NumberChanged += subscriber.OnNumberChanged;
NumberChangedEventArgs numberArg = new NumberChangedEventArgs(  );
 numberArg.Number = 4;
publisher.FireNewNumberEvent(numberArg);
这里发布者显然是不知道numberArg的实际类型的。我们将对实际参数的转换封装在MySubscriber类的OnNumberChanged方法中。
鉴于以上几点,.NET定义了几个委托类型。在参数满足要求时,我们可以直接使用他们:
public delegate void EventHandler(object sender,EventArgs eventArgs);
public delegate void EventHandler<E>(object sender,E e)
                                                       where E : EventArgs;

Defining Custom Event Arguments
如果我们不希望参数在传递的过程中被事件接受者修改,可以将参数设为:readonly或是使用只读property。
public class NumberEventArgs1 : EventArgs
    {  public readonly int Number;
public NumberEventArgs1(int number){Number = number; }
     }

The Generic Event Handler
泛类委托在用作事件有很大的用途。假设所有的事件类型都有void返回类型,事件之间的唯一不同就是参数类型及其个数。我们可以通过使用泛类作为参数而消除不同事件间参数的不同。由此,我们可以通过声明一个泛类委托(包含起覆载)作为所有事件的事件管理器。Example: 
public event GenericEventHandler<MyPublisher,MyArgs> MyEvent;
在这个事件声明中,我们具体化两个参数的泛类委托,第一个参数用一指明事件发布者,第二个参数指明传入的其它参数。

Publishing Events Defensively
异常抛出是基于委托的事件存在的一个问题。C#中,当发布者访问一个不含目标方法的事件(The target list is empty),就会抛出异常。一个不被控制的异常会传递给事件的发布者,为此我们应该总是将事件的发布放在try/finally之内。
public void FireEvent(  )
  {
 try  { if(MyEvent != null) MyEvent(this,EventArgs.Empty); }
     catch{ //Handle exceptions }
    }
上面的代码作用:当有一个接收者异常传递到publisher时将中止发布。有些时候我们希望在有一个接收端异常传递到publisher时,仍然继续发布,只是将该接收者从publisher的目标方法中移除:此时,可以通过调用方法GetInvocationList逐个方法publisher目标对象列表并实现try/finally:
public class MyPublisher
    {
       public event EventHandler MyEvent;
       public void FireEvent(  )
       {
          if(MyEvent == null) { return; }
          Delegate[] delegates = MyEvent.GetInvocationList(  );
          foreach(Delegate del in delegates)
          {
             EventHandler sink = (EventHandler)del;
             try  { sink(this,EventArgs.Empty); }
             catch{ … }
          }
       }
    }
这里我们通过调用GetInvocationList方法将MyEvent事件的每一个目标方法封装在单独的EventHandler类对象中。

The EventsHelper class
使用GetInvocationList方法处理异常不能重用,在每一个发布类中都必须重新编写代码。为此,我们可以使用EventHelper类将代码封装在一个静态方法中,通过调用该静态方法逐个方法委托的每一个目标方法。
public static class EventsHelper
    {
       public static void Fire(Delegate del,params object[] args)
       {
          if(del == null) { return; }
          Delegate[] delegates = del.GetInvocationList(  );
          foreach(Delegate sink in delegates)
          {
try{sink.DynamicInvoke(args); }
catch{}
          }
       }
    }
静态方法Fire的参数del是我们要发布的事件,而params(C#的关键字,为ParamsArray)参数则是del事件发布需要的参数数组。DynamicInvoke是Delegate类定义的一个方法。其形如:
       public object DynamicInvoke(object[] args);
注:params:allows inlining of objects as parameters。
使用EventHelper类发布事件代码相对就简单多了,我们的发布者一般都可以定义形如:
public class MyPublisher
    {
       public event MyEventHandler MyEvent;
       public void FireEvent(ArgType1 arg1, ArgType2 arg2, … )
       {
          EventsHelper.Fire(MyEvent,arg1,arg2,… );
       }
    }
其中MyEvent为委托类型事件。也就是说,我们再也不用为处理异常而烦恼。然而在这段代码中又引入了新的问题,参数类型的安全问题。由于EventsHelper.Fire()的params标示的参数是不确定类型的,如我们可以将上面FireEvent方法改写为:
    public void FireEvent(ArgType arg1, ArgType arg2, … )
       {
          EventsHelper.Fire(MyEvent,”not”, 2, … );
       }
我们可以将EventsHelper同泛类委托结合起来解决这个问题。
    public static class EventsHelper
    {
       public static void UnsafeFire(Delegate del,params object[] args)
       {//同前面定义的Fire完全相同}
       public static void Fire(GenericEventHandler del)
       {
          UnsafeFire(del);
       }
       public static void Fire<T>(GenericEventHandler<T> del,T t)
       {
          UnsafeFire(del,t); 
       }
….
}
这里我们通过使用泛类约束UnsageFire的参数,以保证传入的参数同委托类型相匹配。

Event Accessors
前面我们都将事件作为一个公共成员来操作,实际上我们也可以将其设为私有成员,通过事件访问器来设置。
MyEventHandler m_MyEvent;
       public event MyEventHandler MyEvent 
       {   
          add       {  m_MyEvent += value;   }   
          remove    {  m_MyEvent -= value;   } 
       }
我们发布的事件将是m_MyEvent,而在客户端代码中操作(+=、-=、=)的是事件访问器MyEvent。

Managing Large Numbers of Events
EventHandlerList 类(System.ComponentModel)是一个存储key/value对的现行链表。Key:一表别具体事件的object对象;value:Delegate类对象实例。
public sealed class EventHandlerList : IDisposable
       {
        public EventHandlerList(  );
        public Delegate this[object key]{get;set;}
        public void AddHandler(object key, Delegate value);
        public void AddHandlers(EventHandlerList listToAddFrom);
        public void RemoveHandler(object key, Delegate value);
        public virtual void Dispose(  );
     }
我们可以通过AddHandler向该链表添加事件。如果要发布一个事件,首先通过事件索引器取得事件对象,继而可以像往常一样的操作该事件对象。MyButton类就包含如下部分代码:
    public class MyButton
    {
      EventHandlerList m_EventList;
      public MyButton(  )
       { m_EventList = new EventHandlerList(  ); }
      public event ClickEventHandler Click 
{    //Click事件使用字符窜“Click”作为其在链表中的关键字
     //并通过事件访问器访问链表。
          add   {  m_EventList.AddHandler("Click",value);    }   
          remove {  m_EventList.RemoveHandler("Click",value); } 
       }
       void FireClick(  )
       {  //发布事件,先通过关键字取得具体事件
          ClickEventHandler handler = m_EventList["Click"] as ClickEventHandler;
          EventsHelper.Fire(handler,this,EventArgs.Empty);
       }
由于EventHandlerList链表的关键字是object对象,我们不一定非要用字符窜作为关键字。而且对于某些访问频繁的事件我们可以预先定义关键字。

Writing Sink Interfaces
Consider the case where a subscriber wishes to subscribe to a set of events. Why should it make multiple potentially expensive calls to set up and tear down the connections? Why does the subscriber need to know about the event accessors in the first place?What if the subscriber wants to receive events on an entire interface, instead of individual methods?

    public interface IMySubscriber
    {
       void OnEvent1(object sender,EventArgs eventArgs);
       void OnEvent2(object sender,EventArgs eventArgs);
       void OnEvent3(object sender,EventArgs eventArgs);
}
//实现接口的接收器定义
    public class MySubscriber : IMySubscriber
    {
       public void OnEvent1(object sender,EventArgs eventArgs){...}
       public void OnEvent2(object sender,EventArgs eventArgs){...}
       public void OnEvent3(object sender,EventArgs eventArgs){...}
    }
    [Flags]
    public enum EventType
    {
       OnEvent1,    OnEvent2,      OnEvent3,
       OnAllEvents = OnEvent1|OnEvent2|OnEvent3
    }
    //发布类的定义
    using MyEventHandler = GenericEventHandler<object,EventArgs>;
    public class MyPublisher
    {
        MyEventHandler m_Event1;   
MyEventHandler m_Event2;
        MyEventHandler m_Event3;
        //在发布类添加事件接受者
        public void Subscribe(IMySubscriber subscriber,EventType eventType)
        {
            if((eventType & EventType.OnEvent1) == EventType.OnEvent1)
              {  m_Event1 += subscriber.OnEvent1;  }
            if((eventType & EventType.OnEvent2) == EventType.OnEvent2)
              {  m_Event2 += subscriber.OnEvent2;  }
            if((eventType & EventType.OnEvent3) == EventType.OnEvent3)
              {  m_Event3 += subscriber.OnEvent3;}
        }
        public void Unsubscribe(IMySubscriber subscriber,EventType eventType)
        {
            if((eventType & EventType.OnEvent1) == EventType.OnEvent1)
               {  m_Event1 -= subscriber.OnEvent1;  }
            if((eventType & EventType.OnEvent2) == EventType.OnEvent2)
               {  m_Event2 -= subscriber.OnEvent2;   }
            if((eventType & EventType.OnEvent3) == EventType.OnEvent3)
               {  m_Event3 -= subscriber.OnEvent3;  }
        }
        public void FireEvent(EventType eventType)
        {
            if((eventType & EventType.OnEvent1) == EventType.OnEvent1)
               {  EventsHelper.Fire(m_Event1,this,EventArgs.Empty);  }
            if((eventType & EventType.OnEvent2) == EventType.OnEvent2)
               {  EventsHelper.Fire(m_Event2,this,EventArgs.Empty); }
            if((eventType & EventType.OnEvent3) == EventType.OnEvent3)
               {  EventsHelper.Fire(m_Event3,this,EventArgs.Empty);  }
        }
    }
在这里我们将委托类对象实例的目标方法的赋值封装在publisher类中,对于客户端来说,我们隐藏了其具体细节。要向publisher添加事件接收者我们只能通过调用Subscribe方法实现。
其客户端代码:
MyPublisher publisher = new MyPublisher(  );
    IMySubscriber subscriber   = new MySubscriber(  );
    //Subscribe to events 1 and 2
    publisher.Subscribe(subscriber,EventType.OnEvent1|EventType.OnEvent2);
    //Fire just event 1
publisher.FireEvent(EventType.OnEvent1);
当然这个例子也只是一个样板,仍有很多情况没有考虑到。如:有多个事件接收者,显然一个接口只能服务一个Subscriber类,而若我们要添加其它的事件接收者就比较麻烦。另外我们也不可能知道未来会有怎么样的使劲类型要添加,也就不可能在发布类中包括所有这些类型。
第七章  异步调用
当客户端调用对象的一个方法时,往往先执行完所调用的方法后才返回客户端代码的执行。
You want control to return immediately to the client, while the object executes the called method in the background and then somehow lets the client know that the method has completed execution. Such an execution mode is called asynchronous method invocation.

Requirements for an Asynchronous Mechanism
用作同步/异步调用的组件应该有相同的代码;
只有在客户端才能确定时同步调用还是异步调用,也就是说,在客户端同步调用和异步调用的代码时不相同的;
客户端能够执行多个异步调用并能在同一个进程拥有多个异步调用,并且能够辨别它们;
组件能够服务并发调用;
如果异步调用包含out/ref参数,必须编写代码控制它们;
在组件必须传递给客户端,任何异步调用的方法执行中遇到的异常都必须传递到客户端代码中;
异步调用应当简单容易实现。

When the client issues an asynchronous method call, it can then choose to:
work and block until completion;
work and poll for completion;
work and wait for a predetermined amount of time;
Wait simultaneously for completion of multiple methods;
Receive notification when the method has completed.

Revisiting Delegates
A delegate is nothing more than a type-safe method reference. The delegate is used to delegate the act of calling a method on an object (or a static method on a class) from the client to the delegate class.
By default, when you use a delegate to invoke the methods, the delegate blocks the caller untill all target methods return. However, the delegate can alse be used to invoke methods asynchronously. In fact, delegates are actually compiled to classes which all are derived from MulticastDelegate. As a example:
public sealed class MyDelegate : MulticastDelegate
    {
        public MyDelegate(Object target,int methodPtr){...}
        public virtual ReturnType Invoke(ArgTye1 arg1, … ){...}
        public virtual IAsyncResult BeginInvoke(ArgType1 arg1, …,
AsyncCallback callback,object asyncState) {...}
        public virtual int EndInvoke(IAsyncResult result) {...}
}
Thereinto, the methods BeginInvoke and EndInvoke are just for asynchronous invocation.
Asynchronous Call Programming Models
To support Asynchronous invocation, Multiple threads are required. Spun off a new thread for every asynchronous invocation would be a waste of system resource and a performace penalty. Hence, .NET thread pool come up.
Using BeginInvoke( ) and EndInvoke( )
由编译器生成的这两个方法的形如:
public virtual IAsyncResult BeginInvoke(<input and input/output parameters>,
                                    AsyncCallback callback, object asyncState);
public virtual <return value> EndInvoke(<output and input/output parameters>,
                          AsyncResult asyncResult);
BeginInvoke方法的in/out/ref参数是同源委托的参数完全相对应的,而另外两个不再其源委托签名中的参数意义为:
Callback:指向接受方法完成通知这一事件的回调函数的委托对象;
AsyncState:任何方法执行完毕所包含信息的object对象。
当然这两个参数并不是必须的,我们可以取值null。
EndInvoke方法的返回值同源委托类型相同,所有BeginInvoke方法中的out/ref参数都出现在这个方法的相应位置上。其中AsyncResult参数则时BeginInvoke方法的返回值。例:
Calculator calculator = new Calculator(  );
        BinaryOperation oppDel = calculator.Add;
        int result = oppDel.EndInvoke( oppDel.BeginInvoke(2,3,null,null) );

IAsyncResult interface
BeginInvoke方法返回一个实现了IAsyncResult接口的类对象实例。该接口定义如下:
public interface IAsyncResult
      {
        object AsyncState{get;}
        WaitHandle AsyncWaitHandle{get;}
        bool CompletedSynchronously{get;}
        bool IsCompleted{get;}
 }
EndInvoke( ) blocks its caller until the method it's waiting for (identified by the IAsyncResult object passed in) returns.
The same delegate object (with exactly one target method) can invoke multiple asynchronous calls on the target method. The caller can distinguish between the different pending calls using each unique IAsyncResult object returned from BeginInvoke( ).
IAsyncResult不只是用作EndInvoke()方法的参数,you can use it to get the state object parameter of BeginInvoke( ), you can wait for the method completion, and you can get the original delegate used to invoke the call.
当我们使用基于委托的异步调用时,必须注意:
a、 在每次异步操作中,b、 EndInvoke只能被调用一次;尝试调用多次EndInvoke会引发异常:InvalidOperationException。
c、 尽管委托类可以管理多个目标d、 方法,e、 但在异步操作时,f、 我们只能包含一个目标g、 方法(为此我们可以使用=初始化委托目标h、 方法链表,而i、 不j、 使用+=);
k、 我们只能将由BeginInvoke方法返回的IAsyncResult参数传入相同l、 的委托对象,m、 否则异常:InvalidOperationException。


The AsyncResult class
经常我们会在一个客户端(某端代码)初始化异步调用,而在另一个客户端(另一端代码)调用EndIvoke()。为此必须保存IAsyncResult对象或将其传送给另一个客户端。我们可以通过使用AsyncResult类的属性AsyncDelegate来优化这个问题。该类位于命名空间:
System.Runtime.Remoting.Messaging
该类的声明如下:
public class AsyncResult : IAsyncResult, IMessageSink
    {
       //IAsyncResult implementation
       public virtual object AsyncState{get;}
       public virtual WaitHandle AsyncWaitHandle{get;}
       public virtual bool CompletedSynchronously{get;}
       public virtual bool IsCompleted{get;}
       //Other properties
       public bool EndInvokeCalled{get; set;}
       public virtual object AsyncDelegate{get;}
       /* IMessageSink implementation */
}
BeignInvoke返回的IAsyncResult接口对象实际上是个AsyncResult类对象实例,该对象实例是一指向调用BeginInvoke委托的引用。为此在需要异步调用的客户端我们可以添加一AsyncResult变量,将BeginInvoke的返回值赋给该变量,这样也就可以在另外的客户端通过使用该变量而获得BeginInvoke的返回值。
// Client 1
AsyncResultProperty = oppDel.BeginInvoke(2,3,null,null);
//Client 2
AsyncResult asyncResult = (AsyncResult) Client1.AsyncResultProperty;
OriginalDelegateType a = (OriginalDelegate) asyncResult.AsyncDelegate;
ReturnType result = a.EndInvoke (Client1.AsyncResultProperty);
使用AsyncResult对象使得EndInvoke同BeginInvoke联系变得松弛,而且也是的BeginInvoke的返回值作为一单独的Fields存在,我们可以同时在多个代码块中同时的操作其返回值。

Polling or waiting for completion
如果客户端想要检查(异步调用)方法是否执行完毕,或是只会等待一段时间(如果还没右执行成,则中止等待),或是先作些其它的事情而后继续等待,我们应该如何的处理?
BeginInvoke方法的IAsyncResult返回值的实际类型包含一个AsyncWaitHandler属性,其类型是WaitHandler(WaitHandler实际上是.NET封装的Windows等待事件句柄)。该类型包含重载方法。WaitOne()就是其中一个。AsyncWaitHandler使用实例:
asyncResult.AsyncWaitHandle.WaitOne(  ); //This may block
int result = oppDel.EndInvoke(asyncResult); //This will not block
在这段代码中,我们将原本调用EndInvoke时暂停客户端进程转移到WaitOne方法调用时。这里的EndInVoke同一般客户端代码没有任何区别。使用AsyncWaitHandler主要是为控制代码等待后台进程,在异步调用中,我们不能在客户代码中控制异步调用的执行,甚至也不知道异步调用方法是否开始执行。我们所作的指示通过BeginInvoke将我们要的工作托负给后台进程,然后继续我们想要干的事情。为此,我们只能通过BeginInvoke的IAsyncResult返回值进行检测,例使用该接口的IsCompleted查看方法是否执行完毕,通过AsyncWaitHandler调用WaitOne等。

Using Completion Callback Methods
回调函数不同于前面几种方法,我们既然不能控制异步调用的执行,为什不讲控制完全的交给服务端?
在异步调用中使用回调函数思想:客户代码提供一方法,当异步调用结束时,.NET回调该方法。
BeginInvoke含有一个AsyncCallBack类型参数,该类型定义在System命名空间:
public delegate void AsyncCallback(IAsyncResult asyncResult);
使用回调样例:
delegateObj.BeginInvoke(2,3,OnCallBackMethod,null);
如此,我们就可以将所有需要BeginInvoke返回值的处理全部封装在OnCallBackMethod方法中。
    由于CallBack调用是执行在线程池中的一个线程上的,所以我们还得注意同步问题。

Performing Asynchronous Operations Without Delegates
使用基于委托的异步调用要求定义一个同我们要异步调用的方法相匹配的委托。这里我们定义一种将要异步调用的操作封装进一个类中,该类实现了Begin<operation>、End<operation>方法,这两个方法具有同BeginInvoke和EndInvoke相同的方法签名。使用该方法实例:System.IO命名空间中Stream类中:
public abstract class Stream : MarshalByRefObject,IDisposable
    {
       public virtual int Read(byte[]buffer,int offset,int count);
       public virtual IAsyncResult BeginRead(byte[]buffer,int offset,int count,
                             AsyncCallback callback,object state);
       public virtual int EndRead(IAsyncResult asyncResult);
       public virtual void Write(byte[]buffer,int offset,int count);
       public virtual IAsyncResult BeginWrite(byte[]buffer,int offset,int count,
                        AsyncCallback callback,object state);
       public virtual void EndWrite(IAsyncResult asyncResult);
       /* Other methods and properties */
    }
在该类中就实现了两个异步调用的方法,这不同BeginInvoke,我们不在将异步调用委托给委托类型对象实例,而是在自己类中实例功能类似方法。
使用命令行工具WSDL.exe生成的web service proxy 类包含的异步调用也是一个将异步调用封装在类中的例子:
using System.Web.Services;
      public class Calculator
      {
       [WebMethod]
       public int Add(int argument1,int argument2)
        {  return argument1 + argument2;  }
         //Other methods
  }

Asynchronous Error Handling
当异步调用遇到错误柄抛出异常时,.NET的处理方案:当异步调用抛出异常时,.NET捕抓该异常,在客户端调用EndInvoke时,.NET重新抛出异常以让客户端获得该异常的控制。如果异步调用提供了回调函数,则异常将在回调函数中处理。例下面就时一异步调用的回调函数:
void OnMethodCompletion(IAsyncResult asyncResult)
   {
      AsyncResult resultObj = (AsyncResult)asyncResult;
      Debug.Assert(resultObj.EndInvokeCalled == false);
      BinaryOperation oppDel  = (BinaryOperation)resultObj.AsyncDelegate;
      try
      {
         int result = 0;
         result = oppDel.EndInvoke(asyncResult);
         Trace.WriteLine("Operation returned " + result);
      }
      catch(DivideByZeroException exception)
      {  Trace.WriteLine(exception.Message); }
   }

Asynchronous Events
基于委托事件的发布后存在一个问题,客户代码要等待所有的事件接受这处理完毕才能继续执行其它的任务,如果某个事件接收者在处理事件时抛出异常,这将阻止其它事件接受者。为此,我们引入了事件的异步发布。首先通过委托类的GetInvocationList将委托对象的目标方法分散到多个委托对象中:
Delegate[] delegates = EventOccuried.GetInvocationList(  );
然后逐个调用数组delegates中的委托的BeginInvoke方法:
   foreach(Delegate del in delegates)
      {
         EventOccuriedEventHandler sink = (EventOccuried EventHandler)del;
         sink.BeginInvoke(number,null,null);
      }

Asynchronous Invocation Pitfalls
在异步调用是存在以下几个问题:
线程并发问题,异步调用的对象最好是线程安全的;
Thread-Pool Exhaustion;
繁杂的构造方法,我们通常将需要初始化的异步调用部分放在Initialize方法中,而不是在构造方法内;
调用BeginInvoke的返回值包含一个AsyncWaitHandler属性,默认情况下,该属性时在垃圾回收时释放。我们必须显式的释放:
AsyncWaitHandler.Close();
当然我们以可以将该代码放入回调函数中。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值