Edge.js:讓.NET和Node.js代碼比翼齊飛


通過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<object>>實例。

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<object>> 委托。這種機制為Node.js和.NET互相傳遞數據提供了足夠的靈活性。同時,它需要.NET代碼異步執行,以便於和單線程的Node.js代碼自然地集成在一起。這是Func<object,Task<object>>委托如何映射於Node.js異步模型概念:

互操作模式並不禁止你訪問.NET framework的任何部分,但是它往往會要求你額外編寫一個適配器層以暴露所需的.NET功能如同Func<object,Task<object>> 委托。這個適配器層要求你明確地定位.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<object>>委托。這個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<object>>型的委托實例,它(第5行)的實現包含了第3行在閉包中定義的本地變量。當Edge.js將這個Func<object,Task<object>>實例封送為JavaScript函數回傳給Node.js,並將其分配給第9行的counter變量的時候,這個JavaScript的counter函數有效的涵蓋了CLR狀態下的閉包。這點在第10-11行得到了充分的證明。這兩行兩次調用counter函數,結果返回的是一個不斷增加的值。這是由於每次調用第5行實現的Func<object,Task<object>>都會使得第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#PythonPowerShell的支持。通過語言擴展模型你能很容易的添加其他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

查看中文原文Edge.js:讓.NET和Node.js代碼比翼齊飛


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM