写点什么

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:4511779
用户头像

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

关注

评论

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

【堡垒机】云堡垒机可以安装在外部数据库上吗?

行云管家

数据库 IT运维 云堡垒机 运维安全

直播预告 | 博睿学院:算法平台底座-数据湖应用

博睿数据

数据湖 可观测性 智能运维 博睿数据 博睿学院

图解Redis和Zookeeper分布式锁 | 京东云技术团队

京东科技开发者

redis zookeeper 分布式锁 zookeeper分布式锁 企业号 5 月 PK 榜

Java高并发难题一网打尽,全网最全的高并发设计文档

Java 架构 系统设计 高并发

ClickHouse进阶|如何自研一款企业级高性能网关组件?

字节跳动数据平台

数据库 字节跳动 Clickhouse 企业网关

【云计算】云存储是什么意思?与本地存储有什么区别?

行云管家

云计算 云存储 云管理 云支出

景区共享电动车投放:助力打造智慧景区

共享电单车厂家

共享电单车投放 校园共享电单车 景区共享电动车 共享电动车合作 共享电单车厂家

C端用户体验度量实战篇-京东快递小程序体验度量全面升级 | 京东云技术团队

京东科技开发者

用户体验 用户体验设计 企业号 5 月 PK 榜 京东小程序

已膜拜,GitHub大佬的微服务资源库太强了,每份学习手册都优质详细

Java Kubernetes 微服务 Spring Cloud Spring Boot

完美!啃透P9大佬这份完整版的《并发编程宝典》,成为Offer收割机

Java 并发编程 高并发

借生态力量助力人工智能发展 英特尔这些年做了哪些事?

E科讯

巅峰对谈:迈向 AGI 时代,除了优秀的大模型,还需要什么?丨Fabarta&蓝驰创投

Fabarta

人工智能 图数据库 AI大模型 AGI 图智能

火山引擎DataLeap:如何构建一套完整、易用的数据标准体系

字节跳动数据平台

大数据 数据治理 数据标准 数据研发

ShareSDK Android端合规指南

MobTech袤博科技

复盘的价值是什么?

老张

复盘 复盘归因

软件测试 | JMeter函数和变量

测吧(北京)科技有限公司

测试

可视化探索开源项目的 contributor 关系

NebulaGraph

开源

万众瞩目的Nautilus Chain即将上线主网,生态正式起航

鳄鱼视界

MoE 系列(五)|Envoy Go 扩展之内存安全

SOFAStack

golang 开发者 后端 网关 C++

起猛了!从Github大佬白嫖的分布式进阶宝典,啃完感觉能吊锤面试官

Java 架构 分布式

DB-GPT: Github 两周2.6k star 数据库领域的GPT来了~

csunny

GPT autogpt LLMs

基于AIGC的京东购物助手的技术方案设想 | 京东云技术团队

京东科技开发者

人工智能 智能客服 AIGC 企业号 5 月 PK 榜

网络性能问题排查思路

蓝胖子的编程梦

TCP 网络 问题排查 问题定位 问题解析

软件测试中的维恩图详解

测吧(北京)科技有限公司

测试

软件测试 |JMeter怎样引用函数和变量

测吧(北京)科技有限公司

测试

Wallys/Qualcomm network chip/ipq9574/ipq9554/wireless connectivity solutions.

Cindy-wallys

ipq9554 ipq9574

被性能优化撂倒无数次后的顿悟!465页调优笔记助力大厂面试之旅

Java 性能优化 性能调优

软件测试/测试开发丨Web自动化 option 常用操作headless无头浏览器

测试人

程序员 软件测试 自动化测试 测试开发

烂怂if-else代码优化方案 | 京东云技术团队

京东科技开发者

Java 代码优化 if-else 企业号 5 月 PK 榜

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