委托和方法的异步调用

来源:互联网 发布:窄斑凤尾蛱蝶 标本淘宝 编辑:程序博客网 时间:2024/05/23 12:00

委托和方法的异步调用

 

在通常情况下,如果需要异步执行一个耗时的操作,我们会新建一个线程,然后让这个线程去执行代码.但是对于每一个异步调用都通过创建线程来进行操作显然会对性能产生一定的影响,同时操作相对也会变得繁琐..NET中可以通过委托进行方法的异步调用,就是说客户端在异步调用方法时,本身并不会因为方法的调用而中断,而是从线程池中抓取一个线程去执行该方法,自身线程(主线程)在完成抓取线程这一过程之后,继续执行下面的代码,这样就实现了代码的并行执行.使用线程池的好处就是避免了频繁的进行异步调用时创建,销毁线程的开销.

 

如同上述所说,当在委托对象上调用BeginInvoke(),便进行了一个异步的方法调用.上面的例子是在事件的发布和订阅这一过程中使用了异步调用,而事件发布者和订阅者之间是松耦合的,发布者通常不需要知道订阅者方法的情况;而当使用异步调用时,在更多情况下是为了提升系统的性能,而并非专用于事件的发布和订阅这一编程模型.而在这种情况下使用异步编程时,就需要进行更过的控制,比如当异步执行方法的方法结束时通知客户端,返回异步执行方法的返回值等.

 

先来看这样一段代码:

namespace 委托和方法的异步调用{    class Program    {        static void Main(string[] args)        {            Console.WriteLine("Client application started!\n");            Thread.CurrentThread.Name = "Main Thread";             Calculator cal = new Calculator();            int result = cal.Add(2,5);            Console.WriteLine("Result: {0}\n",result);            //做些事情,模拟需要执行3秒钟            for (int i = 0; i < 4; 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();        }    }    public class Calculator    {        public int Add(int x, int y)        {            if (Thread.CurrentThread.IsThreadPoolThread)            {                Thread.CurrentThread.Name = "Pool Thread";            }            Console.WriteLine("Method invoked!");             //执行某些事情,模拟需要执行两秒钟            for (int i = 0; i < 3; i++)            {                Thread.Sleep(TimeSpan.FromSeconds(i));                Console.WriteLine("{0}: Add executed {1} seconds",Thread.CurrentThread.Name,i);            }            Console.WriteLine("Method complete!");            return x + y;        }    }} 


上面代码有几个关于线程的操作,不理解看下面的解释:

1.Thread.Sleep(),它会让执行当前代码的线程暂停一段时间(如果对线程的概念比较陌生,可以理解为让程序的执行暂停一段时间),以毫秒为单位,比如Thread.Sleep(1000),将会让线程暂停1秒钟.在上面使用了它的重载方法,个人认为如果使用TimeSpan.FromSeconds(1)可读性会高一些.

2.Thread.CurrentThread.Name,通过这个属性可以设置,获取执行当前代码的线程名称,值得注意的是这个属性只能设置一次,如果设置了两次,会抛出异常.

3.Thread.IsThreadPoolThread,可以判断执行当前代码的线程是否为线程池中的线程.

 

通过这几个方法和属性,有助于我们更好的调试异步调用方法.上面代码中除了加入了一些对线程的操作以外没啥特别之处了.我们建立了一个Calaulator,它只有一个Add方法,模拟这个方法需要执行2秒钟时间,并且每个一秒进行一次输出.而在客户端,使用result变量保存了方法的返回值并进行了打印.随后,再次模拟了客户端程序接下来操作需要两秒时间.运行这段时间,自己可以研究一下输出.

 

如果执行这段代码,会看到这些输出并不是一瞬间完成的,而是执行了大概5秒钟的时间,因为线程是串行执行的,所以在执行完Add()方法只会才会继续执行客户端剩下的代码.

 

下面我们来说一个AddDelagate委托,并使用BeginInvoke()方法来异步调用它.在上面已经说过,BeginInvoke()除了最后两个参数为AsyncCallback类型和object类型以外,前面的参数类型和个数与委托定义相同.另外BeginIncoke()方法反悔了一个实现了IAsyncResult接口的对象(实际上就是一个AsyncResult类型实例,注意这里IAsyncResultAsyncResult是不同的,他们均包含在.NET框架中).

 

AsyncResult的用途有如下几个:传递参数,它包含了对调用了BeginInvoke()的委托的引用;它还包含了BeginInvoke()的最后一个object类型的参数;它可以鉴别出是哪个方法的哪一次调用,因为通过同一个委托变量可以对同一个方法调用多次.

 

EndInvoke()方法接受IAsyncResult类型的对象(以及refout类型参数),所以在调用BeginInvoke()之后,需要保留IAsyncResult,以便在调用EndInvoke()时进行传递.这里最重要的就是EndInvoke()方法的返回值,他就是方法的返回值.除此之外,当客户端调用EndInvoke(),如果异步调用的方法没有执行完毕,则会中断当前线程而去等待该方法,只有当异步方法执行完毕之后才会继续执行后面的代码.所以在调用完BeginInvoke()后立即执行EndInvoke()是没有意义的.我们通常在尽可能早的时候调用BeginInvoke(),然后在需要方法的返回值的时候再去调用EndInvoke(),或者是根据情况在晚些时候调用.看看下面的代码:

 

namespace 委托和方法的异步调用{    public delegate int AddDelegate(int x, int y);    class Program    {        static void Main(string[] args)        {            Console.WriteLine("Client application started!\n");            Thread.CurrentThread.Name = "Main Thread";             Calculator cal = new Calculator();            AddDelegate del = new AddDelegate(cal.Add);            IAsyncResult asyncResult = del.BeginInvoke(2,5,null,null);//异步调用方法                        //做些事情,模拟需要执行3秒钟            for (int i = 0; i < 4; i++)            {                Thread.Sleep(TimeSpan.FromSeconds(i));                Console.WriteLine("{0} : Client executed {1} second(s).",Thread.CurrentThread.Name,i);            }            int rnt = del.EndInvoke(asyncResult);            Console.WriteLine("Result: {0}\n",rnt);            Console.WriteLine("Press any key to exit...");            Console.ReadKey();        }    }    public class Calculator    {        public int Add(int x, int y)        {            if (Thread.CurrentThread.IsThreadPoolThread)            {                Thread.CurrentThread.Name = "Pool Thread";            }            Console.WriteLine("Method invoked!");             //执行某些事情,模拟需要执行两秒钟            for (int i = 0; i < 3; i++)            {                Thread.Sleep(TimeSpan.FromSeconds(i));                Console.WriteLine("{0}: Add executed {1} seconds",Thread.CurrentThread.Name,i);            }            Console.WriteLine("Method complete!");            return x + y;        }    }} 

可以看一下输出,执行完这段代码只需要3,两个for循环所产生的输出交替进行,这也说明了这两段代码并行执行的情况,可以看到Add()方法是由线程池中的线程在执行,因为Thread.CurrentThread.IsThreadPoolThread返回了true,同时将该线程命名为了Pool Thread.另外可以看到通过EndInvoke()方法得到了返回值.

 

有时候呢,可能会将获得返回值的操作方法另一段代码或客户端去执行,而不是像上面那样直接写在BeginInvoke()的后面.比如说在Program中新建一个方法GetReturn(),此时可以通过AsyncResultAsyncDelegate获得del委托对象,然后再在其上调用EndInvoke()方法,这也说明了AsyncResult可以唯一的获取到与它相关的调用了的方法(或者也可以理解成委托对象).

 

BeginInvoke的另外两个参数分别是AsyncCallbackobject类型,其中AsyncCallback是一个委托类型,它用于方法的回调,也就是说当异步方法执行完毕时自动进行调用的方法.它的定义如下

public delegate void AsyncCallback(IAsyncResult ar);

object类型用于传递任何想要的数据类型,它可以通过IAsyncResultAsyncState属性获得.

 

下面的代码将获取方法返回值,打印返回值的操作放到了OnAddComplete()回调方法中:

 

namespace 委托和方法的异步调用{    public delegate int AddDelegate(int x, int y);    class Program    {        static void Main(string[] args)        {            Console.WriteLine("Client application started!\n");            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 = 0; i < 4; 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}\n",Thread.CurrentThread.Name,rtn,data);        }    }    public class Calculator    {        public int Add(int x, int y)        {            if (Thread.CurrentThread.IsThreadPoolThread)            {                Thread.CurrentThread.Name = "Pool Thread";            }            Console.WriteLine("Method invoked!");             //执行某些事情,模拟需要执行两秒钟            for (int i = 0; i < 3; i++)            {                Thread.Sleep(TimeSpan.FromSeconds(i));                Console.WriteLine("{0}: Add executed {1} seconds",Thread.CurrentThread.Name,i);            }            Console.WriteLine("Method complete!");            return x + y;        }    }}

观察一下输出,这里有几个需要注意的地方:一是在调用BeginInvoke()后不再需要保存IAsyncResult,因为AsyncCallback委托将该对象定义在了回调方法的参数列表中;二是在OnAddComplete()方法中获得了调用BeginInvoke()时最后一个参数传递的值,字符串”Any data you want to pass”;三是执行回调方法的线程并非客户端线程Main Thread,二是来自线程池中的线程Pool Thread.在调用EndInvoke()的时候有可能会抛出异常,所以应该将它放在Try/catch.

0 0
原创粉丝点击