浅谈C#中的异步编程

实现异步编程有4种方法可供选择,这4种访求实际上也对应着4种异步调用的模式,分为“等待”和“回调”两大类。
  Title一、使用EndInvoke;
  二、使用WaitHanle;
  三、轮询;
  四、回调。
  一、使用EndInvoke
  当使用BeginInvoke异步调用方法时,如果方法未执行完,EndInvoke方法就会一直阻塞,直到被调用的方法执行完毕,如下面的代码:
  Ellic's Code
 

1 using System;
  2 using System.Threading;
  3 namespace MetadataSample
  4 {
  5     class Program
  6     {
  7         //声明一个委托类型
  8         public delegate void PrintDelegate(string content);
  9         public static void Main(string[] args)
  10         {
  11             int threadId = Thread.CurrentThread.ManagedThreadId;
  12             PrintDelegate printDelegate = Program.Print;
  13             Console.WriteLine("[主线程id:{0}]\t开始调用打印方法...",threadId);
  14             IAsyncResult result = printDelegate.BeginInvoke("Hello world",null,null);
  15             printDelegate.EndInvoke(result);
  16
  17             Console.Write("Press any key to continue . . . ");
  18             Console.ReadKey(true);
  19         }
  20         public static void Print(string content)
  21         {
  22             int threadId=Thread.CurrentThread.ManagedThreadId;
  23             Console.WriteLine("[当前线程id:{0}]\t{1}",threadId,content);
  24             System.Threading.Thread.Sleep(2000);
  25             Console.WriteLine("[当前线程id:{0}]\t打印方法调用完毕.",threadId);
  26         }
  27     }
  28 }

 

 

  知识点回顾:
  1、委托类型
  委托类似于C/C++中的函数指针,它能够引用函数,只不过在C#中,委托是一个对象,并且是类型安全的,避免了函数指针的不安全性。一个委托类型的变量可以引用一个或多个方法,这些方法由委托存放于一个调用列表中,当调用一个委托类型的变量即相当于依次调用它的“调用列表”中的方法。委托是一种引用类型。
  可以被引用的方法必须要满足如下规则:
  Θ方法的签名和委托一致,比如方法参数的个数和类型;
  Θ方法的返回值和委托一致。
  委托的声明与实例化:
  Ellic's Code
 

1 using System;
  2 namespace DelegateSample
  3 {
  4     public delegate void DoProcess(string msg);
  5     class DelegateSample
  6     {
  7         void Process(string msg)
  8         {
  9             Console.WriteLine("Process:{0}",msg );
  10         }
  11         public static void Main(string[] args)
  12         {
  13             DelegateSample sample = new DelegateSample();
  14             DoProcess process = new DoProcess(sample.Process);
  15             //DoProcess process = sample.Process;
  16             process("测试数据");
  17             Console.Write("Press any key to continue . . . ");
  18             Console.ReadKey(true);
  19         }
  20     }
  21 }
  2、IAsyncResult接口
  IasyncResult接口定义了异步操作状态应该提供的属性,它的源代码如下:
  1 public interface IAsyncResult
  2 {
  3     object AsycState{get;}
  4     WaitHandle AsyncWaitHandle{get;}
  5     bool CompletedSynchronously{get;}
  6     bool IsCompleted{get;}
  7 }

 

 

  这些属性都是只读属性,它们的含义如下:
  

属性

返回类型

说明

AsycState

object

此属性返回一个对象,该对象是启动异步操作的方法的最后一个参数

AsyncWaitHandle

WaitHandle

获取用于等待异步操作完成的WaitHandle

CompletedSynchronously

bool

获取一个值,该值指示异步操作是否同步完成

IsCompleted

bool

获取一个值,该值指示异步操作是否已完成

  关于IAndsyncResult接口需要补充的是,BeginInvoke方法的返回类型以及EndInvoke方法的唯一参数均为IasyncResult接口类型。
  二、使用WaitHandle
  除了上面提到的方法,我们还可以使用WainHandle类型的WaitOne方法。WaitOne方法有5个重载:
  n bool WaitOne()
  n bool WaitOne(int millisecondsTimeout)
  n bool WaitOne(TimeSpan timeout)
  n bool WaitOne(int millisecondsTimeout, bool exitContext)
  n bool WaitOne(TimeSpan timeout,bool exitContext)
  其中,第一个不带参数的重载相当于WaitOne(-1,false),第一个参数表示等待的毫秒数,-1表示无限期等待,第二个参数表示在等待前是否退出上下文的同步域,并在稍后进行重新获取,是则为TRUE,否则为FALSE。
  这些重载的核心实现为第四个重载,其他的重载就是对参数类型或个数的改变。运行代码如下:
  Ellic's Code
  

1 using System;
  2 using System.Threading;
  3
  4 namespace MetadataSample
  5 {
  6     class Program
  7     {
  8         //声明一个委托类型
  9         public delegate void PrintDelegate(string content);
  10         public static void Main(string[] args)
  11         {
  12             int threadId = Thread.CurrentThread.ManagedThreadId;
  13             PrintDelegate printDelegate = Program.Print;
  14             Console.WriteLine("[主线程id:{0}]\t开始调用打印方法...",threadId);
  15             IAsyncResult result = printDelegate.BeginInvoke("Hello world",null,null);
  16             //printDelegate.EndInvoke(result);
  17             result.AsyncWaitHandle.WaitOne(5000,false);
  18
  19             Console.Write("Press any key to continue . . . ");
  20             Console.ReadKey(true);
  21         }
  22         public static void Print(string content)
  23         {
  24             int threadId=Thread.CurrentThread.ManagedThreadId;
  25             Console.WriteLine("[当前线程id:{0}]\t{1}",threadId,content);
  26             System.Threading.Thread.Sleep(2000);
  27             Console.WriteLine("[当前线程id:{0}]\t打印方法调用完毕.",threadId);
  28         }
  29     }
  30 }
  可以看到,与EndInvoke类似,只是用WaitOne函数代码了EndInvoke而已。
  三、轮询
  之前提到的两种方法,只能等下异步方法执行完毕,在完毕之前没有任何提示信息,整个程序就像没有响应一样,用户体验不好,可以通过检查IasyncResult类型的IsCompleted属性来检查异步调用是否完成,如果没有完成,则可以适时地显示一些提示信息,如下面的代码:
  Ellic's Code
  1 using System;
  2 using System.Threading;
  3 namespace MetadataSample
  4 {
  5     class Program
  6     {
  7         //声明一个委托类型
  8         public delegate void PrintDelegate(string content);
  9         public static void Main(string[] args)
  10         {
  11             int threadId = Thread.CurrentThread.ManagedThreadId;
  12             PrintDelegate printDelegate = Program.Print;
  13             Console.WriteLine("[主线程id:{0}]\t开始调用打印方法...",threadId);
  14             IAsyncResult result = printDelegate.BeginInvoke("Hello world",null,null);
  15             while (!result.IsCompleted)
  16             {
  17                 Console.WriteLine(" . ");
  18                 Thread.Sleep(500);
  19             }
  20             Console.Write("Press any key to continue . . . ");
  21             Console.ReadKey(true);
  22         }
  23         public static void Print(string content)
  24         {
  25             int threadId=Thread.CurrentThread.ManagedThreadId;
  26             Console.WriteLine("[当前线程id:{0}]\t{1}",threadId,content);
  27             System.Threading.Thread.Sleep(2000);
  28             Console.WriteLine("[当前线程id:{0}]\t打印方法调用完毕.",threadId);
  29         }
  30     }
  31 }

 

  结果如下:


  四、回调
  之前三种方法者在等待异步方法执行完毕后才能拿到执行的结果,期间主线程均处于等待状态。回调和它们最大的区别是,在调用BeginInvoke时只要提供了回调方法,那么主线程就不必要再等待异步线程工作完毕,异步线程在工作结束后会主动调用我们提供的回调方法,并在回调方法中做相应的处理,例如显示异步调用的结果。
  先看到之前那段调用BeginInvoke的代码:
  IAsyncResult result = printDelegate.BeginInvoke("Hello world",null,null);
  其中,第1个参数是委托签名中的参数,后面2个参数实际是我们在回调方法中要用到的,它们分别是:
  AsyncCallback callback
  object @object
  前者就是回调方法,它要求回调方法的签名必须符合以下条件:
  返回类型为void;
  参数列表只有1个参数,且为IAsyncResult 类型。
  如:void callbackMethod(IasyncResult asyncResult)
  回调方法代码如下:
  Ellic's Code
  

1 using System;
  2 using System.Threading;
  3 namespace MetadataSample
  4 {
  5     class Program
  6     {
  7         //声明一个委托类型
  8         public delegate void PrintDelegate(string content);
  9         public static void Main(string[] args)
  10         {
  11             int threadId = Thread.CurrentThread.ManagedThreadId;
  12             PrintDelegate printDelegate = Program.Print;
  13             Console.WriteLine("[主线程id:{0}]\t开始调用打印方法...",threadId);
  14             IAsyncResult result = printDelegate.BeginInvoke("Hello world",PrintComplete,printDelegate);
  15             Thread.Sleep(10000);
  16
  17             Console.Write("Press any key to continue . . . ");
  18             Console.ReadKey(true);
  19         }
  20         public static void Print(string content)
  21         {
  22             int threadId=Thread.CurrentThread.ManagedThreadId;
  23             Console.WriteLine("[当前线程id:{0}]\t{1}",threadId,content);
  24             System.Threading.Thread.Sleep(1000);
  25
  26         }
  27
  28         private static void PrintComplete(IAsyncResult asyncResult)
  29         {
  30             if(null == asyncResult)
  31             {
  32                 throw new ArgumentNullException();
  33             }
  34             int threadId = Thread.CurrentThread.ManagedThreadId;
  35             (asyncResult.AsyncState as PrintDelegate).EndInvoke(asyncResult);
  36             Console.WriteLine("[当前线程id:{0}]\t打印方法调用完毕.",threadId);
  37         }
  38     }
  39 }

 

  上面的四种方法就是自己在复习C#时学到的新知识,记录下来。

posted @ 2012-09-05 14:01  许明吉博客  阅读(485)  评论(0编辑  收藏  举报