Node.js 与 .net 中的异步/等待
Posted
技术标签:
【中文标题】Node.js 与 .net 中的异步/等待【英文标题】:Node.js vs Async/await in .net 【发布时间】:2014-02-15 16:49:45 【问题描述】:谁能解释/重定向我,Node.js 的异步模型(非阻塞线程)与任何其他语言(例如 c# 处理 I/O 的异步方式)有什么区别。在我看来,两者都是同一型号。请建议。
【问题讨论】:
await
帮助您使用异步,而不是编写大量回调。
【参考方案1】:
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 关键字使异步代码线性化,并让您比任何其他编程语言更好地避免“回调地狱”。您只需尝试一下,之后您将永远不会以其他方式进行操作。您只需编写使用异步操作的代码,不必担心可读性,因为看起来您编写任何其他代码。 请观看此视频:
-
Async programming deep dive
Async in ASP.NET
Understanding async and Awaitable Tasks
编辑: 由于 Node.js V8 javascript 引擎支持生成器,defined in ECMAScript 6 Draft,JavaScript 代码中的“回调地狱”也可以轻松避免。 It brings some form of async/await to life in JavaScript
【讨论】:
错字:然后在任何方面都好【参考方案2】: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>();
【讨论】:
【参考方案3】:两个模型非常相似。有两个主要区别,其中一个很快就会消失(对于“很快”的某些定义)。
一个区别是 Node.js 是异步单线程的,而 ASP.NET 是异步多线程的。这意味着 Node.js 代码可以做出一些简化假设,因为 all 您的代码总是在同一个确切的线程上运行。因此,当您的 ASP.NET 代码 await
s 时,它可能会在 不同 线程上恢复,而避免线程本地状态之类的事情取决于您。
但是,同样的差异也是 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
以显式启用生成器支持。
【讨论】:
"...代码等待,它可能会在不同的线程上恢复,您可以避免诸如线程本地状态之类的事情..." 不是 async/await 也结束与 UI 线程上的调用者方法相同的线程? @PKV:ASP.NET 上没有 UI 线程。有一个请求上下文,ASP.NET 线程池线程在处理请求时进入。然后,线程在返回线程池时退出请求上下文。await
将确保方法在相同的 request context 上恢复,而不是相同的 thread。
you'll still end up with really awkward code when you do anything non-trivial
。我不能同意。使用像 async.js 这样的库,您可以在 JS 中编写极其优雅的异步代码,即使非常复杂。
@UpTheCreek:各种库有不同的技术/解决方案;完全取决于您想要做什么。复杂性来自于将实际逻辑拆分为多个延续。这就是生成器解决的复杂性。
@Spacemonkey:我不会说“更好”。不同,当然。更适合高性能服务器,绝对。但是编写正确的多线程代码比编写正确的单线程代码更难。所以,(一旦 JS 正式获得 async
)我认为 Node 会更容易编写。谁知道呢,也许最终它会因此而获胜。 JS 得到了一些不错的(也是非常需要的)改进,包括async
。【参考方案4】:
使用 nodejs,所有请求都进入事件队列。 Node 的事件循环使用单个线程来处理事件队列中的项目,完成所有非 IO 工作,并将所有 IO 绑定工作发送到 C++ 线程池(使用 javascript 回调来管理异步)。然后 C++ 线程将其结果添加到事件队列中。
与 ASP.NET 的区别(这两个几乎适用于所有允许异步 IO 的 Web 服务器)是:
-
ASP.NET 为每个传入请求使用不同的线程,因此您会获得上下文切换的开销
.NET 不会强制您使用异步来执行 IO 绑定的工作,因此它不像 nodejs 那样惯用,其中 IO 绑定的 api 调用实际上是异步的(带有回调)
.NET' "await-async" add's a step at compile time to add "callbacks",所以你可以写线性代码(不传递回调函数),与nodejs相比
网络上有很多地方描述节点的架构,但这里有一些东西:http://johanndutoit.net/presentations/2013/02/gdg-capetown-nodejs-workshop-23-feb-2013/index.html#1
【讨论】:
嘿,我明白你在这里的意思。所以应该理解它,对于“n”个传入请求:如果所有 n 个请求都需要 I/O,ASP.Net 会创建“n”个线程和 nodejs 也会创建“n”个线程? @PKV: Threads are not required for asynchronous I/O,正如我在博客中所描述的那样。 Node.js 和async
ASP.NET 都是如此。
@billy:是的,但它只在应该有异步 API 但被忽略且只有同步 API 的情况下使用它。在这种情况下,Node.js 会将同步 API 包装在线程池线程中(当操作系统异步执行实际操作时该线程被阻塞)。因此,Node.js 线程池是针对不完整 API 的一种解决方法;它通常不用于异步 I/O。以上是关于Node.js 与 .net 中的异步/等待的主要内容,如果未能解决你的问题,请参考以下文章
node.js 可以编写一个方法,以便可以两种方式调用它 - 即使用回调或异步/等待?