美文网首页
C#与ES5中异步编程执行顺序的简单类比

C#与ES5中异步编程执行顺序的简单类比

作者: 温振刚做笔记的地方 | 来源:发表于2018-05-05 17:53 被阅读0次

    一、前言

    突然想到的,感觉可以帮助理解。

    二、前期准备工作

    这次C#异步编程的样例在控制台中演示,而ES5使用Asp.net WebApi作为后端、jQuery作为工具进行演示。
    首先在解决方案中新建两个项目,一个用于C#,一个用于Ajax后端请求的WebApi。

    如图 image.png

    然后修改WebApiDemo项目中Program.cs文件的BuildWebHost方法,用于控制绑定的端口。

    public static IWebHost BuildWebHost(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>()
                    .UseUrls("http://localhost:4399")
                    .Build();
    

    如图


    image.png

    然后在WebApiDemo项目中的wwwroot再添加两个新文件,一个html页面和一个js,html页面引用jQuery1.8版本和js文件

    jQuery1.8百度CDN:http://libs.baidu.com/jquery/1.8.3/jquery.min.js

    image.png htmlpage.html文件
    Async.js文件

    修改Startup.cs中的Configure方法,添加使用静态文件(不添加的话不能在网站中查看html页面)

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                app.UseFileServer();
                app.UseMvc();
            }
    
    image.png

    以控制台自托管形式运行WebApiDemo(或者Ctrl+F5)


    用控制台而非IIS能及时获取更多信息
    成功运行

    由于修改了host端口,所以运行的时候程序并不会自动打开默认浏览器,本文用Chrome来进行访问

    用浏览器打开网址http://localhost:4399/api/values和新建的页面http://localhost:4399/htmlpage.html,如图则前期工作完成

    WebApi正常运行
    jQuery正常加载,注意网址

    三、开始编程咯

    首先弄一个C#的异步方法看看吧。
    打开AsyncConsoleDemo项目的Program.cs,覆盖里面的代码

    using System;
    using System.Threading.Tasks;
    
    namespace AsyncConsoleDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("程序开始");//1
                Task<int> i = GetNumberAfter_X_Seconds(10);//2
    
                Console.WriteLine("哇喔");//4-2
                Console.WriteLine(i.Result);//6-2
    
                Console.WriteLine("诶嘿");//7
                Console.ReadLine();//8
            }
    
            
            public static async Task<int> GetNumberAfter_X_Seconds(int X)
            {
                Console.WriteLine("开始获取一个整数");//3
                await Task.Delay(TimeSpan.FromSeconds(X));//4-1
                Console.Write($"{X}秒后,结果是:");//5
    
                return await Task.FromResult(X);//6-1
            }
        }
    }
    
    

    执行结果如图


    image.png

    我们再看看这个异步方法在jQuery1.8中的Ajax如何实现的吧

    首先我们先在控制器ValuesController.cs里修改带id参数的Get方法,并添加一个和控制台项目差不多的GetNumberAfter_X_Seconds方法,只不过这个方法去掉了控制台输出。

            // GET api/values/5
            [HttpGet("{id}")]
            public string Get(int id)
            {
                Task<int> num = GetNumberAfter_X_Seconds(id);
                return num.Result.ToString();
            }
    
            public static async Task<int> GetNumberAfter_X_Seconds(int X)
            {
                await Task.Delay(TimeSpan.FromSeconds(X));
                return await Task.FromResult(X);
            }
    

    生成并运行项目,在网址中输入http://localhost:4399/api/values/10
    看一下是否10秒之后才能响应

    打开网页后,等待10秒才出现10
    我们在后台制造了一个费时的操作,然后现在用jQuery来实现上面控制台的输出顺序,打开Async.js
    将代码换成如下
    $(function ()
    {
        console.log("Ajax开始");
        GetNumberFromServer(10);
    
        console.log("哇喔");
    })
    
    function GetNumberFromServer(Seconds) {
        console.log("开始通过服务器获取一个整数");
        $.ajax({
            type: "get",
            url: "http://localhost:4399/api/values/" + Seconds,
            data: "",
            dataType: "json",
            success: function (result) { 
                console.log(result + "秒后,结果是:" + result);
                console.log("诶嘿");
            },
            error: function (err) {
                console.log(err);
            }
        });
    }
    
    

    打开http://localhost:4399/htmlpage.html,以下是运行结果:

    还原了控制台的输出顺序

    四、对比代码

    代码对比图 输出连线图

    通过代码的对比图可以看到,除了最后的两句话(也就是输出i.Result和“诶嘿”)不同之外,其他代码放置的位置一模一样。
    从这里我们也可以看到,await关键字的作用,相当于生成了一个回调函数,而这个回调函数的方法体,就是await后面的语句

    await关键词告诉主程序:要等我弄完这件事之后,才继续做下面的事情,现在我还没完成,先帮我记下来吧。
    然后程序回答:好的~
    说完就将这一整段挂起运行,并做好标记。继续运行下面的语句,也就是Main方法,因为Main方法中后续语句未被标记await(这里是输出“哇喔”)。

    另外Main方法中调用了i.Result只读属性,这个Task<T>.Result属性当Task<T>未结束的时候会阻塞,导致后面的“诶嘿”不能运行。

    道理我都懂,js最后的“诶嘿”输出可以像C#那样写在$(function())主程序中吗?

    可以实现,先上代码

    var num = null;
    
    $(function ()
    {
        console.log("Ajax开始");
        GetNumberFromServer(10);
    
        console.log("哇喔");
    
        ShowInfo();
    })
    
    function GetNumberFromServer(Seconds) {
        console.log("开始通过服务器获取一个整数");
        $.ajax({
            type: "get",
            url: "http://localhost:4399/api/values/" + Seconds,
            data: "",
            dataType: "json",
            success: function (result) { 
                console.log(result + "秒后,结果是:");
                num = result;
            },
            error: function (err) {
                console.log(err);
            }
        });
    }
    
    function ShowInfo() {
        if (num === null) {
            setTimeout(function () {
                ShowInfo()
            }, 100);
        } else {
            console.log(num);
            console.log("诶嘿");
        }
    }
    

    可以看到我们用了一个全局变量(污染全局了好吗),和一个定时器递归查询才做到C#控制台的行为,代价相当大,函数间的跳转也增加了阅读的难度。

    五、结论

    通过类比可以我们意识到,C#中async和await这一对好兄弟,在我们的看不到的背后,实现了一个巨复杂的状态机,才能使我们将异步编程能够像同步编程那样编写,并脱离了回调地狱。

    六、后续

    C#基于任务的异步模式 (TAP)中对于C#异步编程写得巨详细,而且有不少高级用法
    例如Task.WhenAll[],就是开启一堆任务,当任务全部完成时所要做的事情。
    这个也可以类比到jQuery中的deferred对象使用。
    会写吗?我也母鸡。

    相关文章

      网友评论

          本文标题:C#与ES5中异步编程执行顺序的简单类比

          本文链接:https://www.haomeiwen.com/subject/yhterftx.html