写点什么

Edge.js:让.NET 和 Node.js 代码比翼齐飞

Tomasz Janczuk Explains Edge.js

  • 2013-09-16
  • 本文字数:4340 字

    阅读完需:约 14 分钟

通过 Edge.js 项目,你可以在一个进程中同时运行 Node.js 和.NET 代码。在本文中,我将会论述这个项目背后的动机,并描述 Edge.js 提供的基本机制。随后将探讨一些 Edge.js 应用场景,它在这些场景中可以为你开发 Node.js 程序提供帮助。

为何要使用 Edge.js?

虽然许多应用程序只能用 Node.js 编写,不过有些情况下又需要综合 Node.js 和.NET 两者的优点。基于以下几个理由,你想要在程序中使用.NET 和 Node.js:.NET 框架和 NuGet 包提供了一个丰富的功能生态系统,它很好地补充了 Node.js 和 NPM 模块;可能你希望在 Node.js 程序中重用某些现成的.NET 组件;也可能想使用多线程 CLR 运行 CPU 密集型的计算,而这绝非是单线程的 Node.js 所擅长的;又或者你可能优先选择使用.NET 框架和 C#而不是使用 C/C++ 编写原生的 Node.js 扩展来访问那些尚未通过 Node.js 暴露的操作系统机制。

一旦你决定在程序中使用 Node.js 和.NET,那么你必须将 Node.js 和.NET 的组件用进程壁垒将两者分离开来,并建立某种形式的进程间通信的机制,比如说 HTTP:

Edge.js 提供另一种类似的组建异构系统的方式。它允许你在单一进程中同时运行 Node.js 和.NET 代码,并且提供了 V8 和 CLR 之间的互操作机制。

使用 Edge.js 可以在一个进程中运行 Node.js 和.NET,而不用将其分割为两个进程,这样有两个主要的好处:更好的性能和更低的复杂性。

某个场景的性能测试显示,从 Node.js 向 C#发出的进程内 Edge.js 请求比两个进程间通过 HTTP 发送的相同请求快 32 倍。与两个进程和进程间的通信信道相比,只处理一个单独的进程,明显降低了你需要解决的部署和维护的复杂性。

.NET 欢迎 Node.js

接下来我将用一个基础实例讲解 Edge.js 的关键概念,这个例子是从 Node.js 向 C#发送请求。

第 1 行引入事先从 NPM 安装的 edge 模块。Edge.js 是一个原生的 Node.js 组件。Edge.js 的特殊之处在于,它被加载的时候便在 node.exe 进程内部开始代管 CLR。

edge 模块暴露了一个名为 func 的单函数。在高层次上,该函数以 CLR 代码为参数,然后返回一个 JavaScript 函数作为 CLR 代码的代理。func 函数接受多种格式的 CLR 代码,从源代码,文件名,到预编译的 CLR 都可以。在上面的 3-8 行中,程序指定了一个异步的 Lambda 表达式作为 C#文本代码。Edge.js 提取出那段代码并将其编译为内存中的 CLR 程序集。然后它围绕着第 3 行的 CLR 代码(分配给 hello 变量的)创建并返回了一个 JavaScript 代理函数。需要注意的是,这个编译过程在每次调用 edge.func 函数时都会执行一次并将结果缓存。此外,如果你用同样的字符串变量调用 edge.func 函数两次,那么就会从缓存中获得相同的 Func<object,Task> 实例。

Edge.js 创建的 hello 函数是 C#代码的代理函数,它在第 10 行由标准的 Node.js 异步模式调用。这个函数接收一个单独参数(Node.js 字符串),并且还有一个接收错误和返回结果的回调函数。输入的参数在第 4 行被传递到 C#异步 Lambda 表达式中,这个表达式在第 6 行将传入值附加到“.NET welcomes”字符串之后。当调用第 10 行的 JavaScript 回调函数的时候,这个 C#中新构造的字符串被 Edge.js 作为 result 参数传递进去。JavaScript 回调函数则将其打印在控制台上:“.NET welcomes Node.js”。

Edge.js 提供了一套进程内 Node.js 和.NET 代码之间规范的互操作模型。它不允许 JavaScript 直接调用任何 CLR 函数。CLR 函数必须是一个 Func<object,Task> 委托。这种机制为 Node.js 和.NET 互相传递数据提供了足够的灵活性。同时,它需要.NET 代码异步执行,以便于和单线程的 Node.js 代码自然地集成在一起。这是 Func<object,Task> 委托如何映射于 Node.js 异步模型概念:

互操作模式并不禁止你访问.NET framework 的任何部分,但是它往往会要求你额外编写一个适配器层以暴露所需的.NET 功能如同 Func<object,Task> 委托。这个适配器层要求你明确地定位.NET 中的阻塞 APIs 的问题所在,它可能将这些运算运行在 CLR 线程池中以避免阻塞 Node.js 事件循环。

数据和功能

虽然 Edge.js 仅仅允许你在 Node.js 和.NET 之间传递一个参数,但是这个参数可能是个复杂类型的。当从 Node.js 请求.NET 代码的时候,Edge.js 可以封送(marshal)所有标准的 JavaScript 类型:从基类型到对象和数组。当从.NET 向 Node.js 传递数据的时候,Edge.js 不但可以封送所有的基本 CLR 类型,而且还可以处理 CLR 对象实例、列表、集合和字典类型。从概念上讲,你可以认为在 V8 和 CLR 之间的数据传递就像是在一个环境中将数据序列化为 JSON,而在另一个环境中对 JSON 进行反序列化。但是,Edge.js 并没有在进程中进行实际的 JSON 序列化过程。相反,它直接在内存中进行 V8 和 CLR 类型系统之间的数据封送,而省略了字符串型中间代码,这个过程远比 JSON 序列化和反序列化更加高效。

Edge.js 通过值进行数据封送,所以当执行过程跨越 V8/CLR 边界时,它会在 V8 或者 CLR 的堆中另外创建一份数据拷贝。这个规则有一处显著的例外:与通过值进行数据封送不同,Edge.js 通过引用来封送函数。让我们通过下面这个例子来说明这个强有力的概念:

在这个例子中,Node.js 调用 addAndMultiplyBy2 的 C#中运行的函数。这个函数获取两个数字,而后返回它们总和的 2 倍。鉴于这个例子的目的,我们假设 C#知道如何做加法但是却并不清楚如何做乘法。C#代码在计算和之后需要回调至 JavaScript 以进行乘法运算。

为了实现这个场景,Node.js 应用程序在第 18-20 行定义一个 multiplyBy2 函数,并在第 23 行调用 addAndMultiplyBy2 函数时将其随同两个运算对象传递至 C#代码。注意 multiplyBy2 函数是如何满足 Edge.js 规范的互操作模式的。这使得 Edge.js 可以在给 multiplyBy2 这个 JavaScript 函数创建.NET 代理,就像是.NET 中的 Func<object,Task> 委托。这个 JavaScript 函数代理接下来被 C#代码在第 10 行调用,用于对第 8-9 行中得到的和执行乘法运算。

遵守规范的互操作模式的函数也可以从.NET 被封送到 Node.js。能够在 V8 和 CLR 中双向封送函数是很强有力的概念,尤其是当掺杂着闭包的时候更是如此。请看下面这个例子:

在第 1-7 行,Edge.js 创造了一个 JavaScript 函数 createCounter,这个是 C# Lambda 表达式的代理。第 9 行中传给 createCounter 函数的的参数在第 3 行被强制转化为一个 C#的本地变量。第 4-5 行的代码比较有趣:C#异步 Lambda 表达式的结果是一个 Func<object,Task> 型的委托实例,它(第 5 行)的实现包含了第 3 行在闭包中定义的本地变量。当 Edge.js 将这个 Func<object,Task> 实例封送为 JavaScript 函数回传给 Node.js,并将其分配给第 9 行的 counter 变量的时候,这个 JavaScript 的 counter 函数有效的涵盖了 CLR 状态下的闭包。这点在第 10-11 行得到了充分的证明。这两行两次调用 counter 函数,结果返回的是一个不断增加的值。这是由于每次调用第 5 行实现的 Func<object,Task> 都会使得第 3 行的本地变量的数值增加。

在 V8 和 CLR 之间封送函数的能力加上闭包的概念是个很强有力的机制。这样.NET 代码就能够暴露 CLR 对象的功能给 Node.js。第三行的本地变量在最后的例子中是一个 Person 类的实例。

让我们一起动手

我们来看几个实际的例子以便了解如何在 Node.js 应用程序中使用 Edge.js。

Node.js 是单线程的架构。如果要保持响应性,那么应用程序中就不能执行阻塞的代码。大部分 Node.js 程序都是在进程外执行 CPU 密集型的运算。外部进程通常使用的技术并不是 Node.js。Edge.js 使得这种场景非常容易实现。它允许你的 Node.js 程序在 Node.js 进程内部的 CLR 线程池中执行 CPU 密集型的逻辑运算。当 CPU 密集型的计算在 CLR 线程池的线程中运行时,V8 线程上的 Node.js 程序仍然是可响应的。一旦 CPU 密集型操作结束,Edge.js 同步线程就在 V8 线程上执行 JavaScript 回调函数。请看这个使用.NET 功能转换图片格式的例子:

convertImageToJpg 函数使用了.NET 中的 System.Drawing 的功能将 PNG 图片转换为 JPG 格式。这是计算密集型的操作,因此第 6 行创建的 C#实现(implementation)调用了 Task.Run 在 CLR 线程池中运行这个转换。当计算执行的时候,进程中的单例(singleton)V8 线程可以处理后续的事件。C#代码随第 6 行的 await 关键字而等待图片转换的完成。只有在图片转换完成之后,convertImageToJpg 在 V8 线程上执行第 14-15 行 JavaScript 回调代码,整个函数才算完成。

另一个让 Edge.js 大显身手的例子是在 MS SQL 中读取数据。现在 Node.js 开发者还没有什么读取 MS SQL 数据的方法可以比.NET Framework 中的 ADO.NET 更加完善和成熟。Edge.js 提供给你一个简单的在 Node.js 程序中利用 ADO.NET 的方法。请看下这个 Node.js 程序:

在第 1 行中,Edge.js 通过编译 sql.csx 文件中的 ADO.NET 代码创建了 sql 函数。这个 sql 函数接受一个 T-SQL 命令构成的字符串,并使用 ADO.NET 异步执行它,然后将结果返回给 Node.js。sql.csx 文件用 C#编写了不到 100 行的 ADO.NET 代码,它支持对 MS SQL 数据库执行 CRUD 四种操作:

在 sql.csx 文件中的实现(implementation)使用异步 ADO.NET 的 API 来访问 MS SQL 数据并执行 Node.js 传给它的 T-SQL 命令。

上面的两个例子仅仅代表了 Edge.js 帮你编写 Node.js 程序的一小部分场景。更多的例子可以参见 Edge.js 的 GitHub 站点。

路线图

Edge.js 是一个遵循 Apache 2.0 协议的开源项目。它目前的开发很活跃,欢迎前来贡献代码。你可以用你的时间和经验来检查工作项目列表。

尽管本文中所有的例子都是使用 C#写的,Edge.js 支持在 Node.js 程序中运行任何 CLR 语言的代码。目前的扩展提供了对脚本语言 F# Python PowerShell 的支持。通过语言扩展模型你能很容易的添加其他 CLR 语言的编译器。

Edge.js 目前需要.NET Framework 环境,因此只能运行在 Windows 上。但是对 Mono 的支持也在积极的开发中,不久就可以在 MacOS 和 *nix 上运行 Edge.js 程序了。

关于作者

Tomasz Janczuk 是微软的一名软件工程师。他目前主要关注 Node.js 和 Windows Azure。在此之前他从事.NET Framework 和网络服务(web services)方面的工作。业余时间里,他在太平洋等地参加了很多户外活动。你可以在 Twitter 上关注他, @tjanczuk ,也可以访问他的 GitHub 页面或者阅读他的博客以获得更多的资讯。

查看英文原文: Run .NET and Node.js code in-process with Edge.js


感谢侯伯薇对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013-09-16 05:4511202
用户头像

发布了 21 篇内容, 共 67425 次阅读, 收获喜欢 1 次。

关注

评论

发布
暂无评论
发现更多内容

🔥🔥🔥 计算机视觉 GPT-4V 它来了!

石头爱分享

VQA openai AIGC GPT MidJourney

EazyDraw for mac(矢量图绘制软件) 11.2.0中文直装版

mac

苹果mac Windows软件 矢量绘图软件 EazyDraw

小灯塔系列-中小企业数字化转型系列研究——CDP测评报告

人称T客

TimeWise-Jira工时管理插件6.0.0发布!对比测评某知名工时插件,谁的数据处理性能更胜一筹?

龙智—DevSecOps解决方案

TimeWise Jira工时管理插件

七张图解锁Mybatis整体脉络,让你轻松拿捏面试官

小小怪下士

Java 程序员 mybatis

如何有效的给出反馈(二)

ShineScrum

反馈 敏捷教练 敏捷教练引导 高管

深度学习与预训练语言的突破

百度开发者中心

自然语言处理 大模型训练 人工智能「

如何利用动态配置中心在JavaAgent中实现微服务的多样化治理

华为云开发者联盟

云计算 后端 云服务 华为云 华为云开发者联盟

分布式事务:XA和Seata的XA模式 | 京东物流技术团队

京东科技开发者

分布式事务 seata XA 企业号10月PK榜

软件测试/测试开发丨AI大模型应用开发实训营,文末领学习资料

测试人

人工智能 大数据 程序员 软件测试

[大厂实践] 重新发明后端子集

俞凡

算法 Google 大厂实践

材质、纹理、贴图的区别和关联

3D建模设计

材质 纹理 贴图

🔥🔥🔥还没搞懂嵌入(Embedding)、微调(Fine-tuning)和提示工程(Prompt Engineering)?

石头爱分享

Embedding openai AIGC GPT-4 prompt 工程

游戏和 NFT 的以太坊代币开发

区块链软件开发推广运营

交易所开发 dapp开发 区块链开发 链游开发 NFT开发

【后台体验】运营后台订单详情设计分享 | 京东云技术团队

京东科技开发者

后台开发 后台管理系统 订单系统 企业号10月PK榜 运营后台

微软首款AI芯片代号“雅典娜”;马斯克四年内将让“星舰”上火星丨 RTE 开发者日报 Vol.61

声网

身为产品经理该如何向客户推广API商品数据接口

Noah

API接口文档 API 安全 API 接口

🔥🔥🔥序幕:AIGC 进入“平民化”时代

石头爱分享

AI openai AIGC GPT-4 MidJourney

Red Giant Trapcode Suite for Mac(红巨星粒子插件) 2024.0.1永久激活版

mac

苹果mac Windows软件 红巨星粒子插件 Red Giant Trapcode Suite

如何精细化管理嵌入式软件项目?ACT汽车电子与软件技术周演讲回顾

龙智—DevSecOps解决方案

中企全球化案例-能源业:“1+2+3+N”,建设全球领先的智慧司库平台

用友BIP

全球司库 中企出海

大模型训练:文本分类的未来之路

百度开发者中心

大模型训练 #人工智能

聊聊JDK19特性之虚拟线程 | 京东云技术团队

京东科技开发者

Java JVM 虚拟线程 jdk19 企业号10月PK榜

Spring AOP 中被代理的对象一定是单例吗?

江南一点雨

Java spring

一文教你如何发挥好 TDengine Grafana 插件作用

TDengine

时序数据库 ​TDengine

深入理解java和dubbo的SPI机制 | 京东物流技术团队

京东科技开发者

Java spi Dubbo SPI 企业号10月PK榜

用友招聘云助力中企出海,充盈全球化人才蓄水池

用友BIP

招聘 中企出海

Edge.js:让.NET和Node.js代码比翼齐飞_C#_Tomasz Janczuk_InfoQ精选文章