雁过请留痕...
代码改变世界

APM异步编程模型的优势

2012-08-21 14:11  xiashengwang  阅读(1270)  评论(2编辑  收藏  举报

我们之所以要花大力气学习APM,就必须要清楚它能解决实际编程中的那些难题。以及现有的技术为什么不行。

简单点说:APM是基于IAsyncResult接口的,采用的BeginXXX和EndXXX的形式来实现异步。

下面这几点就是APM的优势:

1,线程执行是异步的,不会阻塞调用线程。这一点也是我们使用异步的主要目的,我们不就是希望后台处理一些耗时操作吗?

2,任务完成可以得到通知。(非阻塞)

3,任务可以实现进度报告。(需要额外的辅助代码支持)

4,可以有返回值。

5,参数可以是多个。

再看看Thread,ThreadPool,Task能实现上面的几点?这三个中,Task是相对完善的一个,对于第2点它可以通过ContineWith方法来实现,对于第5点,它可以通过Lamda表达式的参数捕获来实现。但总觉得有点怪怪的,没有APM灵活。而对于Thread,ThreadPool来说,除了第1点,其它的都不行。

1,FCL中提供了大量现成的,基于I/O的异步操作实现类。

如,Stream,WebRequest,Dns,SqlCommand等等。对于开发者来说,直接使用它们就可以了。

            WebRequest webRequest = WebRequest.Create("http://www.baidu.com");
            webRequest.BeginGetResponse(result =>
            {
                WebResponse webResponse = null;
                try
                {
                    webResponse = webRequest.EndGetResponse(result);
                    using (System.IO.FileStream fs = new System.IO.FileStream("D:baidu.html", System.IO.FileMode.Create, System.IO.FileAccess.Write))
                    {
                        using (System.IO.Stream webStream = webResponse.GetResponseStream())
                        {
                            webStream.CopyTo(fs);
                        }
                    }
                }
                catch (WebException ex)
                {
                    Console.WriteLine("Failed:" + ex.GetBaseException().Message);
                }
                finally
                {
                    if (webResponse != null) webResponse.Close();
                }
            }, null);

2,将IAsyncResutl转成Task来完成。也是上面的代码的不同版本。

这里就是用TaskFactroy的FromAsync方法将IAsyncResutl转成了Task,你肯定觉得这是多余的,但是Task的优势在于它可以有后续Task,可以按顺序执行,在某些场合,这个功能是非常有用的。

            WebRequest webRequest = WebRequest.Create("http://www.baidu.com");
            TaskFactory factroy = new TaskFactory();
            Task<WebResponse> task = factroy.FromAsync<WebResponse>(webRequest.BeginGetResponse, webRequest.EndGetResponse, TaskCreationOptions.None);
            task.ContinueWith(t => {
                WebResponse webResponse = null;
                try
                {
                    webResponse = t.Result;
                    using (System.IO.FileStream fs = new System.IO.FileStream("D:baidu.html", System.IO.FileMode.Create, System.IO.FileAccess.Write))
                    {
                        using (System.IO.Stream webStream = webResponse.GetResponseStream())
                        {
                            webStream.CopyTo(fs);
                        }
                    }
                }
                catch (WebException ex)
                {
                    Console.WriteLine("Failed:" + ex.GetBaseException().Message);
                }
                finally
                {
                    if (webResponse != null) webResponse.Close();
                }
            
            });

3,用代理类型(delegate)实现异步调用。

上面的代码都是在调用.Net类库实现的APM类。难道我们有个方法需要异步执行,也需要去包装,实现BeginXXX和EndXXX以及相关的接口吗?如果是这样,如此复杂的APM谁还会用。幸好,代理Delegate天生就支持APM模型。我们申明了一个delegate后,编译器就会自动为我们生成BeginInvoke和EndInvoke等方法。于是乎,我们要做的工作就简单多了,声明一个和方法签名一致的带类类型。调用它的BgeinInvoke方法和EndInvoke方法。就是这么简单!

        //假设:这是我们想要异步执行的方法,它有三个参数,和一个自定义类型的返回值
        private UserInfo OurFunction(string param1, string param2, string param3)
        {
            //...
            //...
            UserInfo uInfo = new UserInfo();
            return uInfo;
        }
        //第一步:定义一个和它签名一致的代理类型
        private delegate UserInfo OurFunctionDelegate(string param1, string param2, string param3);

private void MainFunction() { //第二步:声明这个代理类型
OurFunctionDelegate hander = new OurFunctionDelegate(OurFunction); //第三步:调用BgeinInvoke方法,同时传入一个回调函数 hander.BeginInvoke("p1", "p2", "p3", AsyncCallBack, hander); } //第四步:定义一个回调函数 private void AsyncCallBack(IAsyncResult result) { OurFunctionDelegate hander = (OurFunctionDelegate)result.AsyncState; try { //函数异步执行完成,会进入这个函数,调用EndInvoke方法可以得到结果,如有异常,也会在这里抛出 UserInfo info = hander.EndInvoke(result); } catch (Exception) { //处理异步操作的异常,当然,这里的异常应该是ArrgregateException。 } }

注意事项:对于异步调用获取结果的方式,主要有以下几种。

1,利用AsyncCallBack回调函数,它是不阻塞的。(首选)

2,利用IAsyncResult的Result。调用线程遇到这句话会阻塞。

3,利用IAsyncResult的AsyncWaitHandle.WaitOne()。和2一样会阻塞。

4,轮询IAsyncResult的IsCompleted属性。同样会耗CPU资源,阻塞调用线程,如新开一个线程来轮询又浪费线程资源。