• ASP.NET4.5Web API及非同步程序开发系列(1)


    认识非同步程序开发设计模型

    从VS2012开始引入的新的非同步程序设计的支持-------async/await设计模型

    1. 之前的当我们支持非同步作业的时候,往往使用多线程开解决,我们比较熟悉的就是
    2. 执行者:Thread,ThreadPool (线程和线程池,后者有利于资源的有效利用)
    3. 非同步的设计模型:Begin方法/End方法,Async事件/Completed事件(主要是异步委托之类的,我在我以前的博文中有写过专题)
    4. BackgroundWorker控制项
    5. Task Parallel Library

      虽然今天的重点是.NET4.5的async/await设计模式但是由于很多人对于.NET4.0中的Task仍然还是没有接触过,Task也是.NET 4.5 async await的基础概念之一,值得大家花点时间熟悉,那么这里就将他也作为一个附加专题来做一下讲解。

    附属专题:.NET4.0多线程开发之利器---àTask

    到我们在开发SignalR程序的时候,就必须要使用到多线程,假设没有.NET4.5的支持,那么你可能想到的最简单方式就是使用Task,它取代了传统的Thread,TheadPool的写法,能大幅度的简化同步逻辑的写法,颇为便利,下面我们来看几个典型的范例。

    范例1:简单的开始

    Test1()用以另一Thread执行Thread.Sleep()及Console.WriteLine(),效果与ThreadPool.QueueUserWorkItem()相当。

      private static void Test1()
            {
                //Task可以代替TheadPool.QueueUserWorkItem使用
                Task.Factory.StartNew(() =>
                    {
                        Thread.Sleep(2000);
                        Console.WriteLine("Done!");
                    });
                Console.WriteLine("Async Run...");
            }

    StartNew()完会立刻执行下一行,故会先看到Aync Run,1秒后打印出Done。

    范例2:等待各作业完成再继续下一步的应用场境

      同时启动多个作业多工并行(多线程并行),但要等待各作业完成再继续下一步的应用场境传统方式上可通过WaitHandle、AutoResetEvent、ManualResetEvent等机制实现;Task的写法相当简单,建立多個Task对象,再作为Task.WaitAny()或Task.WaitAll()的参数就搞定了! 

    private static void Test2()
            {
                var task1 = Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(3000);
                    Console.WriteLine("Done!(3s)");
                });
                var task2 = Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(5000);
                    Console.WriteLine("Done!(5s)");
                });
                //等待任意作业完成后继续
                Task.WaitAny(task1, task1);
                Console.WriteLine("WaitAny Passed");
                //等待所有作业完成后继续
                Task.WaitAll(task1, task2);
                Console.WriteLine("WaitAll Passed");
            }

    task1耗时3秒、task2耗时5秒,所以3秒后WaitAny()执行完成、5秒后WaitAll()执行完毕。

    范例3:如果要等待多工作业传回结果

    通过StartNew<T>()指定传回类型建立作业,随后以Task.Result取值,不用额外Code就能确保多工作业执行完成后才读取结果继续运行

    private static void Test3()
            {
                var task = Task.Factory.StartNew<string>(() =>
                {
                    Thread.Sleep(2000);
                    return "Done!";
                });
                //使用秒表计时
                Stopwatch sw = new Stopwatch();
                sw.Start();
                //读取task.Result时,会等到作业完成传回值后才继续
                Console.WriteLine("{0}", task.Result);
                sw.Stop();
                //取得task.Result耗时约2秒
                Console.WriteLine("Duration: {0:N0}ms", sw.ElapsedMilliseconds);
            }

    实际执行,要花两秒才能跑完Console.WriteLine("{0}", task.Result),其长度就是Task執行并回传结果的时间。

    范例4:在多工作业完成后接连运行行另一段程序可使用ContinueWith():

       private static void Test4()
            {
                Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("Done!");
                }).ContinueWith(task =>
                {
                    //ContinueWith会等待前面的任务完成才继续
                    Console.WriteLine("In ContinueWith");
                });
                Console.WriteLine("Async Run...");
            }
    如预期,ContinueWith()里的程序会在Task完成后才被执行。

    范例5:多工作业时各段逻辑便会依顺序执行

    .ContinueWith()传回值仍是Task对象,所以我们可以跟jQuery一样连连看,在ContinueWith()後方再接上另一个ContinueWith(),各段逻辑便会依顺序执行。

    static void test5()
            {
                //ContinueWith()可以串接
                  Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(2000);
                    Console.WriteLine("{0:mm:ss}-Done", DateTime.Now);
                })
                .ContinueWith(task =>
                {
                    Console.WriteLine("{0:mm:ss}-ContinueWith 1", DateTime.Now);
                    Thread.Sleep(2000);
                })
                .ContinueWith(task =>
                {
                    Console.WriteLine("{0:mm:ss}-ContinueWith 2", DateTime.Now);
                });
                Console.WriteLine("{0:mm:ss}-Async Run...", DateTime.Now);
            }

    Task耗时两秒,第一個ContinueWith()耗時2秒,最后一个ContinueWith()继续在4秒后执行。

    范例6:Task有监控状态的机制

    ContinueWith()中的Action<Task>都会有一个输入参数,用于以得知前一Task的执行状态,有IsCompleted, IsCanceled, IsFaulted几个属性可用。要取消执行,得借助CancellationTokenSource及其所属CancellationToken类型,做法是在Task中持续呼叫CancellationToken.ThrowIfCancellationRequested(),一旦外部呼叫CancellationTokenSource.Cancel(),便会触发OperationCanceledException,Task有监控此异常状况的机制,将结束作业执行后续ContinueWith(),并指定Task.IsCanceled为True;而当Task程序发送Exception,也会结束触发ContinueWith (),此時Task.IsFaulted为True,ContinueWith()中可通过Task.Exception.InnerExceptions取得错误细节。以下程序同时可测试Task正常、取消及错误三种情景,使用者通过输入1,2或3来决定要测试哪一种。在Task外先声明一个CancellationTokenSource类型,将其中的Token属性当成StartNew()的第二项参数,而Task中則保留最初的五秒可以取消,方法是每隔一秒呼叫一次CancellationToken.ThrowIfCancellationRequested(),当程序外部调用CancellationTokenSource.Cancel(),Task就会結束。5秒后若未取消,再依使用者决定的测试情境return结果或是抛出Exception。ContinueWith()则会检查IsCanceled, IsFaulted等标识,并输出结果。

      private static void Test6()
            {
                CancellationTokenSource cts = new CancellationTokenSource();
                CancellationToken cancelToken = cts.Token;//获取与此CancellationTokenSource关联的CancellationToken
                Console.Write("Test Option 1, 2 or 3 (1-Complete / 2-Cancel / 3-Fault) : ");
                var key = Console.ReadKey();
                Console.WriteLine();
                Task.Factory.StartNew<string>(() =>
                {
                    //保留5秒检测是否要Cancel
                    for (var i = 0; i < 5; i++)
                    {
                        Thread.Sleep(1000);
                        //如cancelToken.IsCancellationRequested
                        //引发OperationCanceledException
                        cancelToken.ThrowIfCancellationRequested();
                    }
                    switch (key.Key)
                    {
                        case ConsoleKey.D1: //选1时
                            return "OK";
                        case ConsoleKey.D3: //选3时
                            throw new ApplicationException("MyFaultException");
                    }
                    return "Unknown Input";
                }, cancelToken).ContinueWith(task =>
                {
                    Console.WriteLine("IsCompleted: {0} IsCanceled: {1} IsFaulted: {2}", task.IsCompleted, task.IsCanceled, task.IsFaulted);
                    if (task.IsCanceled)
                    {
                        Console.WriteLine("Canceled!");
                    }
                    else if (task.IsFaulted)
                    {
                        Console.WriteLine("Faulted!");
                        foreach (Exception e in task.Exception.InnerExceptions)
                        {
                            Console.WriteLine("Error: {0}", e.Message);
                        }
                    }
                    else if (task.IsCompleted)
                    {
                        Console.WriteLine("Completed! Result={0}", task.Result);
                    }
                });
                Console.WriteLine("Async Run...");
                //如果要测Cancel,2秒后触发CancellationTokenSource.Cancel
                if (key.Key == ConsoleKey.D2)
                {
                    Thread.Sleep(2000);
                    cts.Cancel();
                }
            }

    Task能做的事,过去使用Thread/ThreadPool配合Event、WaitHandle一样能办到,但使用Task能以比较简洁的语法完成相同工作,使用.NET 4.0开发多线程时可多加利用。

    到这里,我们继续回到原本的.NET4.5中,首先我们设计几种异步作业新旧写法法进行对比

    利用WebClient类别下载网页内容

    1.使用Async/Complete设计模式

     private static void DownLoadWebPageSourceCode_Old()
            {
                WebClient wc = new WebClient();
                wc.DownloadStringCompleted += CompletedHandler;
                wc.DownloadStringAsync(new Uri("http://www.cnblogs.com/rohelm"));
                while (wc.IsBusy)
                {
                    Console.WriteLine("还没下完,我喝一回茶!");
                }
            }
    
            private static void CompletedHandler(object sender, DownloadStringCompletedEventArgs e)
            {
                Console.WriteLine(e.Result);
            }

    运行效果如下:

    2.使用新的async/await设计模式

     private static async void DownLoadWebPageSourceCode_New()
            {
                WebClient wc = new WebClient();
                Console.WriteLine(await wc.DownloadStringTaskAsync("http://www.cnblogs.com/rohelm"));
            }

    而它的内部实现机制实际上是我们前面的附加专题中提到的Task,我们来查看下这个方法的源码:

    [ComVisible(false), HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)]
    public Task<string> DownloadStringTaskAsync(string address)
    {
        return this.DownloadStringTaskAsync(this.GetUri(address));
    }
     
    [ComVisible(false), HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)]
    public Task<string> DownloadStringTaskAsync(Uri address)
    {
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(address);
        DownloadStringCompletedEventHandler handler = null;
        handler = delegate (object sender, DownloadStringCompletedEventArgs e) {
            this.HandleCompletion<DownloadStringCompletedEventArgs, DownloadStringCompletedEventHandler, string>(tcs, e, args => args.Result, handler, delegate (WebClient webClient, DownloadStringCompletedEventHandler completion) {
                webClient.DownloadStringCompleted -= completion;
            });
        };
        this.DownloadStringCompleted += handler;
        try
        {
            this.DownloadStringAsync(address, tcs);
        }
        catch
        {
            this.DownloadStringCompleted -= handler;
            throw;
        }
        return tcs.Task;
    }

    3.取消非同步的方式

      由于上面我们已经说过它的内部本质还是Task所以它的,取消该非同步作业依旧借助CancellationTokenSource及其所属CancellationToken类型

    private static async Task TryTask()
            {
                CancellationTokenSource cts = new CancellationTokenSource();
                cts.CancelAfter(TimeSpan.FromSeconds(1));
                Task<string> task = Task.Run(() => PirntWords("Hello,Wrold!", cts.Token), cts.Token);
                Console.WriteLine(task.Result);
                await task;
            }
    
            private static string PirntWords(string input, CancellationToken token)
            {
                for (int i = 0; i < 20000000; i++)
                {
                    Console.WriteLine(input);
                    token.ThrowIfCancellationRequested();
                }
                return input;
            }

    网页调用用多个Web服务/WCF服务/Http服务模型

           public async Task<ActionResult> DoAsync()
            {
                ServiceClient1 client1 = new ServiceClient1();
                ServiceClient2 client2 = new ServiceClient2();
                var task1 = client1.GetDataAsync();
                var task2 = client2.GetDataAsync();
                await Task.WhenAll(task1,task2);
                return View("View名称",new DataModel(task1.Result,task2.Rusult));
            }

    是不是发现非常的方便,实用啊!

    未完待续....

      后续内容:

       对WebAPI和WCF的进行一个简单比较,探讨WebAPI的机制,功能,架构,WinFrom Client/WebService Client大数据上传...备注:本文章版权的没有,归.NET使用者共有。

  • 相关阅读:
    添加egit插件
    Git使用教程
    mysql set names 命令和 mysql字符编码问题
    git常用命令
    前端WEB编辑器-------webstrom
    maven 常见错误解决方法
    java -jar 执行 eclipse export 的 jar 包报错处理
    数据库(DBUtils)
    数据库(JDBC、DBUtils)
    rabbitmq Exchange四种模式
  • 原文地址:https://www.cnblogs.com/rohelm/p/3189441.html
Copyright © 2020-2023  润新知