C#异步方法的使用

from:http://www.myext.cn/csharp/a_6765.htmlhtml

也许业内不少高不成低不就的程序员都会对一些知识点会有些迷惑,缘由是日常工做用的少,因此也就决定了你对这个事物的了解程度。今天就来看看C#中异步方法的使用。但愿对你们有所帮助。程序员

--原文编程

一般状况下,若是须要异步执行一个耗时的操做,咱们会新起一个线程,而后让这个线程去执行代码。可是对于每个异步调用都经过建立线程来进行操做显 然会对性能产生必定的影响,同时操做也相对繁琐一些。.Net中能够经过委托进行方法的异步调用,就是说客户端在异步调用方法时,自己并不会由于方法的调 用而中断,而是从线程池中抓取一个线程去执行该方法,自身线程(主线程)在完成抓取线程这一过程以后,继续执行下面的代码,这样就实现了代码的并行执行。 使用线程池的好处就是避免了频繁进行异步调用时建立、销毁线程的开销。app

如同上面所示,当咱们在委托对象上调用BeginInvoke()时,便进行了一个异步的方法调用。而在这种状况下使用异步编程时,就须要进行更多 的控制,好比当异步执行方法的方法结束时通知客户端、返回异步执行方法的返回值等。本节就对BeginInvoke()方法、EndInvoke()方法 和其相关的IAysncResult作一个简单的介绍。异步

NOTE:讨论在客户端程序中异步地调用方法。async

咱们看这样一段代码,它演示了不使用异步调用的一般状况:异步编程

 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秒钟时间。运行这段程序,会产生下面的输出:spa

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...

这里有几个值得注意的地方:一、咱们在调用BeginInvoke()后再也不须要保存IAysncResult了,由于AysncCallback 委托将该对象定义在了回调方法的参数列表中;二、咱们在OnAddComplete()方法中得到了调用BeginInvoke()时最后一个参数传递的 值,字 符串“Any data you want to pass”;三、执行回调方法的线程并不是客户端线程Main Thread,而是来自线程池中的线程Pool Thread。另外如前面所说,在调用EndInvoke()时有可能会抛出异常,因此在应该将它放到try/catch块中,这里我就再也不示范了。

 

结语:这篇文章简单的描述了异步委托方法的使用和说明,本文是从原做者内容中摘抄而来,作了不多的改动,望见谅。

感谢阅读,但愿对你能有所帮助。

原文地址:http://www.cnblogs.com/JimmyZhang/archive/2008/08/22/1274342.html

相关文章
相关标签/搜索