【发布时间】:2014-02-15 16:49:45
【问题描述】:
谁能解释/重定向我,Node.js 的异步模型(非阻塞线程)与任何其他语言(例如 c# 处理 I/O 的异步方式)有什么区别。在我看来,两者都是同一型号。请建议。
【问题讨论】:
-
await帮助您使用异步,而不是编写大量回调。
标签: node.js asynchronous async-await nonblocking
谁能解释/重定向我,Node.js 的异步模型(非阻塞线程)与任何其他语言(例如 c# 处理 I/O 的异步方式)有什么区别。在我看来,两者都是同一型号。请建议。
【问题讨论】:
await 帮助您使用异步,而不是编写大量回调。
标签: node.js asynchronous async-await nonblocking
Node.js 的异步模型和 C# 的 async/await 模型之间的差异是巨大的。具有 Node.js 的异步模型类似于 C# 和 .Net 中称为基于事件的异步模式 (EAP) 的 old 异步模型。 C# 和 .Net 有 3 个异步模型,您可以在 Asynchronous Programming Patterns 阅读它们。 C# 中最现代的异步模型是基于任务的,带有 C# 的 async 和 await 关键字,您可以在 Task-based Asynchronous Pattern 阅读有关它的信息。 C# 的 async/await 关键字使异步代码线性化,并让您比任何其他编程语言更好地避免“回调地狱”。您只需尝试一下,之后您将永远不会以其他方式进行操作。您只需编写使用异步操作的代码,不必担心可读性,因为看起来您编写任何其他代码。 请观看此视频:
请尝试在 C# 和 Node.js 中执行异步操作以进行比较。你会看到不同之处。编辑: 由于 Node.js V8 JavaScript 引擎支持生成器,defined in ECMAScript 6 Draft,JavaScript 代码中的“回调地狱”也可以轻松避免。 It brings some form of async/await to life in JavaScript
【讨论】:
Nodejs 和 .NET 中的异步之间的区别在于对用户代码使用抢占式多任务处理。 .NET 对用户代码使用抢占式多任务处理,而 Nodejs 没有。
Nodejs 使用一个内部线程池来处理 IO 请求,并使用一个线程来执行您的 JS 代码,包括 IO 回调。
使用抢占式多任务 (.NET) 的一个后果是共享状态可以在执行堆栈时被另一个执行堆栈更改。在 Nodejs 中情况并非如此——来自异步操作的回调不能与当前正在执行的堆栈同时运行。 Javascript 中不存在另一个执行堆栈。仅当当前执行堆栈完全退出时,回调才能使用异步操作的结果。这样一来,简单的while(true); 就会挂起 Nodejs,因为在这种情况下,当前堆栈不会退出,并且永远不会启动下一个循环。
要了解区别,请考虑两个示例,一个用于 js,一个用于网络。 var p = new Promise(function(resolve) { setTimeout(resolve, 500, "我的内容"); }); p.then(function(value) { // ... value === "我的内容"
在此代码中,您可以在“开始”异步操作之后安全地放置一个处理程序(then),因为您可以确定,在整个当前调用之前,不会执行由异步操作启动的回调代码堆栈退出。回调在下一个周期中处理。至于计时器回调,它们的处理方式相同。异步计时器事件只是将回调处理放在队列中,以便在下一个循环中处理。
在 .NET 中就不同了。没有周期。有抢占式多任务处理。
ThreadPool.QueueUserWorkItem((o)=>{eventSource.Fire();});
eventSource.Fired += ()=>{
// the following line might never execute, because a parallel execution stack in a thread pool could have already been finished by the time the callback added.
Console.WriteLine("1");
}
这是一个 Hello World .NET a-la Nodejs 代码,用于演示单线程上的异步处理以及使用线程池进行异步 IO,就像 node 一样。 (.NET 包括异步 IO 操作的 TPL 和 IAsyncResult 版本,但在本示例中没有任何区别。无论如何,一切都以线程池上的不同线程结束。)
void Main()
{
// Initializing the test
var filePath = Path.GetTempFileName();
var filePath2 = Path.GetTempFileName();
File.WriteAllText(filePath, "World");
File.WriteAllText(filePath2, "Antipodes");
// Simulate nodejs
var loop = new Loop();
// Initial method code, similar to server.js in Nodejs.
var fs = new FileSystem();
fs.ReadTextFile(loop, filePath, contents=>{
fs.WriteTextFile(loop, filePath, string.Format("Hello, {0}!", contents),
()=>fs.ReadTextFile(loop,filePath,Console.WriteLine));
});
fs.ReadTextFile(loop, filePath2, contents=>{
fs.WriteTextFile(loop, filePath2, string.Format("Hello, {0}!", contents),
()=>fs.ReadTextFile(loop,filePath2,Console.WriteLine));
});
// The first javascript-ish cycle have finished.
// End of a-la nodejs code, but execution have just started.
// First IO operations could have finished already, but not processed by callbacks yet
// Process callbacks
loop.Process();
// Cleanup test
File.Delete(filePath);
File.Delete(filePath2);
}
public class FileSystem
{
public void ReadTextFile(Loop loop, string fileName, Action<string> callback)
{
loop.RegisterOperation();
// simulate async operation with a blocking call on another thread for demo purposes only.
ThreadPool.QueueUserWorkItem(o=>{
Thread.Sleep(new Random().Next(1,100)); // simulate long read time
var contents = File.ReadAllText(fileName);
loop.MakeCallback(()=>{callback(contents);});
});
}
public void WriteTextFile(Loop loop, string fileName, string contents, Action callback)
{
loop.RegisterOperation();
// simulate async operation with a blocking call on another thread for demo purposes only.
ThreadPool.QueueUserWorkItem(o=>{
Thread.Sleep(new Random().Next(1,100)); // simulate long write time
File.WriteAllText(fileName, contents);
loop.MakeCallback(()=>{callback();});
});
}
}
public class Loop
{
public void RegisterOperation()
{
Interlocked.Increment(ref Count);
}
public void MakeCallback(Action clientAction)
{
lock(sync)
{
ActionQueue.Enqueue(()=>{clientAction(); Interlocked.Decrement(ref Count);});
}
}
public void Process()
{
while(Count > 0)
{
Action action = null;
lock(sync)
{
if(ActionQueue.Count > 0)
{
action = ActionQueue.Dequeue();
}
}
if( action!= null )
{
action();
}
else
{
Thread.Sleep(10); // simple way to relax a little bit.
}
}
}
private object sync = new object();
private Int32 Count;
private Queue<Action> ActionQueue = new Queue<Action>();
}
【讨论】:
两个模型非常相似。有两个主要区别,其中一个很快就会消失(对于“很快”的某些定义)。
一个区别是 Node.js 是异步单线程的,而 ASP.NET 是异步多线程的。这意味着 Node.js 代码可以做出一些简化假设,因为 all 您的代码总是在同一个确切的线程上运行。因此,当您的 ASP.NET 代码 awaits 时,它可能会在 不同 线程上恢复,而避免线程本地状态之类的事情取决于您。
但是,同样的差异也是 ASP.NET 的优势,因为这意味着 async ASP.NET 可以开箱即用地扩展到您服务器的全部功能。例如,如果您考虑一台 8 核机器,那么 ASP.NET 可以同时处理(的同步部分)8 个请求。如果您将 Node.js 放在增强型服务器上,那么实际运行 8 个单独的 Node.js 实例并添加诸如 nginx 之类的东西或处理该服务器的路由请求的简单自定义负载均衡器是很常见的。这也意味着,如果您希望在服务器范围内共享其他资源(例如缓存),那么您也需要将它们移出进程。
另一个主要区别实际上是语言的不同,而不是平台的不同。 JavaScript 的异步支持仅限于回调和承诺,即使你使用最好的库,当你做任何不平凡的事情时,你仍然会得到非常尴尬的代码。相比之下,C#/VB 中的async/await 支持允许您编写非常自然的异步代码(更重要的是,可维护异步代码)。
但是,语言差异正在消失。 JavaScript 的下一个版本将引入生成器,它(连同一个帮助程序库)将使 Node.js 中的异步代码就像现在使用 async/await 一样自然。如果你现在想玩“即将推出”的东西,生成器是在 V8 3.19 中添加的,它被滚动到 Node.js 0.11.2(Unstable 分支)中。传递 --harmony 或 --harmony-generators 以显式启用生成器支持。
【讨论】:
await 将确保方法在相同的 request context 上恢复,而不是相同的 thread。
you'll still end up with really awkward code when you do anything non-trivial。我不能同意。使用像 async.js 这样的库,您可以在 JS 中编写极其优雅的异步代码,即使非常复杂。
async)我认为 Node 会更容易编写。谁知道呢,也许最终它会因此而获胜。 JS 得到了一些不错的(也是非常需要的)改进,包括async。
使用 nodejs,所有请求都进入事件队列。 Node 的事件循环使用单个线程来处理事件队列中的项目,完成所有非 IO 工作,并将所有 IO 绑定工作发送到 C++ 线程池(使用 javascript 回调来管理异步)。然后 C++ 线程将其结果添加到事件队列中。
与 ASP.NET 的区别(这两个几乎适用于所有允许异步 IO 的 Web 服务器)是:
网络上有很多地方描述节点的架构,但这里有一些东西:http://johanndutoit.net/presentations/2013/02/gdg-capetown-nodejs-workshop-23-feb-2013/index.html#1
【讨论】:
async ASP.NET 都是如此。