浅谈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#时学到的新知识,记录下来。