委托和方法的异步调用
本人对原作进行了简单的修改,大部分都与原文相同
也许业内很多高不成低不就的程序员都会对一些知识点会有些迷惑,原因是平常工作用的少,所以也就决定了你对这个事物的了解程度。今天就来看看C#中异步方法的使用。希望对大家有所帮助。
--原文
通常情况下,如果需要异步执行一个耗时的操作,我们会新起一个线程,然后让这个线程去执行代码。但是对于每一个异步调用都通过创建线程来进行操作显 然会对性能产生一定的影响,同时操作也相对繁琐一些。.Net中可以通过委托进行方法的异步调用,就是说客户端在异步调用方法时,本身并不会因为方法的调 用而中断,而是从线程池中抓取一个线程去执行该方法,自身线程(主线程)在完成抓取线程这一过程之后,继续执行下面的代码,这样就实现了代码的并行执行。 使用线程池的好处就是避免了频繁进行异步调用时创建、销毁线程的开销。
如同上面所示,当我们在委托对象上调用BeginInvoke()时,便进行了一个异步的方法调用。而在这种情况下使用异步编程时,就需要进行更多的控制,比如当异步执行方法的方法结束时通知客户端、返回异步执行方法的返回值等。本节就对BeginInvoke()方法、EndInvoke()方法和其相关的IAysncResult做一个简单的介绍。
NOTE:讨论在客户端程序中异步地调用方法。
我们看这样一段代码,它演示了不使用异步调用的通常情况:
1 class Program7 { 2 static void Main(string[] args) { 3 4 Console.WriteLine("Client application started! "); 5 Thread.CurrentThread.Name = "Main Thread"; 6 7 Calculator cal = new Calculator(); 8 int result = cal.Add(2, 5); 9 Console.WriteLine("Result: {0} ", result); 10 11 // 做某些其它的事情,模拟需要执行3秒钟 12 for (int i = 1; i <= 3; i++) { 13 Thread.Sleep(TimeSpan.FromSeconds(1)); 14 Console.WriteLine("{0}: Client executed {1} second(s).", 15 Thread.CurrentThread.Name, i); 16 } 17 18 Console.WriteLine(" Press any key to exit..."); 19 Console.ReadKey(); 20 } 21 } 22 23 public class Calculator { 24 public int Add(int x, int y) { 25 if (Thread.CurrentThread.IsThreadPoolThread) { 26 Thread.CurrentThread.Name = "Pool Thread"; 27 } 28 Console.WriteLine("Method invoked!"); 29 30 // 执行某些事情,模拟需要执行2秒钟 31 for (int i = 1; i <= 2; i++) { 32 Thread.Sleep(TimeSpan.FromSeconds(1)); 33 Console.WriteLine("{0}: Add executed {1} second(s).", 34 Thread.CurrentThread.Name, i); 35 } 36 Console.WriteLine("Method complete!"); 37 return x + y; 38 } 39 }
上面代码有几个关于对于线程的操作,如果不了解可以看一下下面的说明,如果你已经了解可以直接跳过:
- Thread.Sleep(), 它会让执行当前代码的线程暂停一段时间(如果你对线程的概念比较陌生,可以理解为使程序的执行暂停一段时间),以毫秒为单位,比如 Thread.Sleep(1000),将会使线程暂停1秒钟。在上面我使用了它的重载方法,个人觉得使用 TimeSpan.FromSeconds(1),可读性更好一些。
- Thread.CurrentThread.Name,通过这个属性可以设置、获取执行当前代码的线程的名称,值得注意的是这个属性只可以设置一次,如果设置两次,会抛出异常。
- Thread.IsThreadPoolThread,可以判断执行当前代码的线程是否为线程池中的线程。
通 过这几个方法和属性,有助于我们更好地调试异步调用方法。上面代码中除了加入了一些对线程的操作以外再没有什么特别之处。我们建了一个 Calculator类,它只有一个Add方法,我们模拟了这个方法需要执行2秒钟时间,并且每隔一秒进行一次输出。而在客户端程序中,我们使用 result变量保存了方法的返回值并进行了打印。随后,我们再次模拟了客户端程序接下来的操作需要执行2秒钟时间。运行这段程序,会产生下面的输出:
Client application started! Method invoked! Main Thread: Add executed 1 second(s). Main Thread: Add executed 2 second(s). Method complete! Result: 7 Main Thread: Client executed 1 second(s). Main Thread: Client executed 2 second(s). Main Thread: Client executed 3 second(s). Press any key to exit...
如果你确实执行了这段代码,会看到这些输出并不是一瞬间输出的,而是执行了大概5秒钟的时间,因为线程是串行执行的,所以在执行完Add()方法之后才会继续客户端剩下的代码。
接下来我们定义一个AddDelegate委托,并使用BeginInvoke()方法来异步地调用它。在上面已经介绍过,BeginInvoke()除了 最后两个参数为AsyncCallback类型和Object类型以外,前面的参数类型和个数与委托的方法定义相同。另外BeginInvoke()方法返回了一个实现了IAsyncResult接口的对象(实际上就是一个AsyncResult(System.Runtime.Remoting.Messaging命名空间里)类型实例,注意这里IAsyncResult和 AysncResult是不同的)。
AsyncResult的用途有这么几个:传递参数,它 包含了对调用了BeginInvoke()的委托的引用;它还包含了BeginInvoke()的最后一个Object类型的参数;它可以鉴别出是哪个方 法的哪一次调用,因为通过同一个委托变量可以对同一个方法调用多次。
EndInvoke()方法接受IAsyncResult类型的对象 (以及ref和out类型参数,这里不讨论了,对它们的处理和返回值类似),所以在调用BeginInvoke()之后,我们需要保留 IAsyncResult,以便在调用EndInvoke()时进行传递。这里最重要的就是EndInvoke()方法的返回值,它就是方法的返回值。除 此以外,当客户端调用EndInvoke()时,如果异步调用的方法没有执行完毕,则会中断当前线程而去等待该方法,只有当异步方法执行完毕后才会继续执 行后面的代码。所以在调用完BeginInvoke()后立即执行EndInvoke()是没有任何意义的。我们通常在尽可能早的时候调用 BeginInvoke(),然后在需要方法的返回值的时候再去调用EndInvoke(),或者是根据情况在晚些时候调用。说了这么多,我们现在看一下 使用异步调用改写后上面的代码吧:
1 public delegate int AddDelegate(int x, int y); 2 3 class Program8 { 4 5 static void Main(string[] args) { 6 7 Console.WriteLine("Client application started! "); 8 Thread.CurrentThread.Name = "Main Thread"; 9 10 Calculator cal = new Calculator(); 11 AddDelegate del = new AddDelegate(cal.Add); 12 IAsyncResult asyncResult = del.BeginInvoke(2,5,null,null); // 异步调用方法 13 14 // 做某些其它的事情,模拟需要执行3秒钟 15 for (int i = 1; i <= 3; i++) { 16 Thread.Sleep(TimeSpan.FromSeconds(i)); 17 Console.WriteLine("{0}: Client executed {1} second(s).", 18 Thread.CurrentThread.Name, i); 19 } 20 21 int rtn = del.EndInvoke(asyncResult); 22 Console.WriteLine("Result: {0} ", rtn); 23 24 Console.WriteLine(" Press any key to exit..."); 25 Console.ReadKey(); 26 } 27 } 28 29 public class Calculator { /* 与上面同,略 */}
此时的输出为:
Client application started! Method invoked! Main Thread: Client executed 1 second(s). Pool Thread: Add executed 1 second(s). Main Thread: Client executed 2 second(s). Pool Thread: Add executed 2 second(s). Method complete! Main Thread: Client executed 3 second(s). Result: 7 Press any key to exit...
现在执行完这段代码只需要3秒钟时间,两个for循环所产生的输出交替进行,这也说明了这两段代码并行执行的情况。可以看到Add()方法是由线程 池中的线程在执行,因为Thread.CurrentThread.IsThreadPoolThread返回了True,同时我们对该线程命名为了 Pool Thread。另外我们可以看到通过EndInvoke()方法得到了返回值。
有时候,我们可能会将获得返回值的操作放到另一段 代码或者客户端去执行,而不是向上面那样直接写在BeginInvoke()的后面。比如说我们在Program中新建一个方法GetReturn(), 此时可以通过AsyncResult的AsyncDelegate获得del委托对象,然后再在其上调用EndInvoke()方法,这也说明了 AsyncResult可以唯一的获取到与它相关的调用了的方法(或者也可以理解成委托对象)。所以上面获取返回值的代码也可以改写成这样:
static int GetReturn(IAsyncResult asyncResult) { AsyncResult result = (AsyncResult)asyncResult; AddDelegate del = (AddDelegate)result.AsyncDelegate; int rtn = del.EndInvoke(asyncResult); return rtn; }
然后再将int rtn = del.EndInvoke(asyncResult);语句改为int rtn = GetReturn(asyncResult);。注意上面IAsyncResult要转换为实际的类型AsyncResult才能访问 AsyncDelegate属性,因为它没有包含在IAsyncResult接口的定义中。
BeginInvoke的另外两个参数分别是AsyncCallback和Object类型,其中AsyncCallback是一个委托类型,它用于方法的回调,即是说当异步方法执行完毕时自动进行调用的方法。它的定义为:
public delegate void AsyncCallback(IAsyncResult ar);
Object类型用于传递任何你想要的数值,它可以通过IAsyncResult的AsyncState属性获得。下面我们将获取方法返回值、打印返回值的操作放到了OnAddComplete()回调方法中:
public delegate int AddDelegate(int x, int y); class Program9 { static void Main(string[] args) { Console.WriteLine("Client application started! "); Thread.CurrentThread.Name = "Main Thread"; Calculator cal = new Calculator(); AddDelegate del = new AddDelegate(cal.Add); string data = "Any data you want to pass."; AsyncCallback callBack = new AsyncCallback(OnAddComplete); del.BeginInvoke(2, 5, callBack, data); // 异步调用方法 // 做某些其它的事情,模拟需要执行3秒钟 for (int i = 1; i <= 3; i++) { Thread.Sleep(TimeSpan.FromSeconds(i)); Console.WriteLine("{0}: Client executed {1} second(s).", Thread.CurrentThread.Name, i); } Console.WriteLine(" Press any key to exit..."); Console.ReadKey(); } static void OnAddComplete(IAsyncResult asyncResult) { AsyncResult result = (AsyncResult)asyncResult; AddDelegate del = (AddDelegate)result.AsyncDelegate; string data = (string)asyncResult.AsyncState; int rtn = del.EndInvoke(asyncResult); Console.WriteLine("{0}: Result, {1}; Data: {2} ", Thread.CurrentThread.Name, rtn, data); } } public class Calculator { /* 与上面同,略 */}
产生的输出为:
Client application started! Method invoked! Main Thread: Client executed 1 second(s). Pool Thread: Add executed 1 second(s). Main Thread: Client executed 2 second(s). Pool Thread: Add executed 2 second(s). Method complete! Pool Thread: Result, 7; Data: Any data you want to pass. Main Thread: Client executed 3 second(s). Press any key to exit...
这里有几个值得注意的地方:1、我们在调用BeginInvoke()后不再需要保存IAysncResult了,因为AysncCallback委托将该对象定义在了回调方法的参数列表中;2、我们在OnAddComplete()方法中获得了调用BeginInvoke()时最后一个参数传递的值,字 符串“Any data you want to pass”;3、执行回调方法的线程并非客户端线程Main Thread,而是来自线程池中的线程Pool Thread。另外如前面所说,在调用EndInvoke()时有可能会抛出异常,所以在应该将它放到try/catch块中,这里我就不再示范了。
结语:这篇文章简单的描述了异步委托方法的使用和说明,本文是从原作者内容中摘抄而来,做了很少的改动,望见谅。
感谢阅读,希望对你能有所帮助。
原文地址:http://www.cnblogs.com/JimmyZhang/archive/2008/08/22/1274342.html