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

一、前言

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

二、前期准备工作

这次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对象使用。
会写吗?我也母鸡。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容

  • 一.非阻塞和异步 借用知乎用户严肃的回答在此总结下,同步和异步是针对消息通信机制,同步代表一个client发出一个...
    Daniel_adu阅读 1,805评论 0 8
  • 什么是异步编程 什么是异步编程呢?举个简单的例子: 上面这段代码中,Main方法中的代码是按照自上而下的顺序执行的...
    雪飞鸿阅读 4,402评论 0 12
  • 本文主要介绍了在 C# 中使用 Async 和 Await 关键字进行异步编程的心得,是入门级的学习笔记。 题解:...
    BossOx阅读 5,710评论 4 27
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,573评论 18 139
  • 你们天天写诗 写诗 有人写过 远方么
    欲上鲤鱼去阅读 145评论 0 0