56

Can someone explain/ redirect me, what is the difference between Node.js's async model(non blocking thread) vs any other language for example c#'s asynchronous way of handling the I/O. This looks to me that both are same model. Kindly suggest.

PKV
  • 773
  • 1
  • 7
  • 17

4 Answers4

94

Both models are very similar. There are two primary differences, one of which is going away soon (for some definition of "soon").

One difference is that Node.js is asynchronously single-threaded, while ASP.NET is asynchronously multi-threaded. This means the Node.js code can make some simplifying assumptions, because all your code always runs on the same exact thread. So when your ASP.NET code awaits, it could possibly resume on a different thread, and it's up to you to avoid things like thread-local state.

However, this same difference is also a strength for ASP.NET, because it means async ASP.NET can scale out-of-the-box up to the full capabilities of your sever. If you consider, say, an 8-core machine, then ASP.NET can process (the synchronous portions of) 8 requests simultaneously. If you put Node.js on a souped-up server, then it's common to actually run 8 separate instances of Node.js and add something like nginx or a simple custom load balancer that handles routing requests for that server. This also means that if you want other resources shared server-wide (e.g., cache), then you'll need to move them out-of-proc as well.

The other major difference is actually a difference in language, not platform. JavaScript's asynchronous support is limited to callbacks and promises, and even if you use the best libraries, you'll still end up with really awkward code when you do anything non-trivial. In contrast, the async/await support in C#/VB allow you to write very natural asynchronous code (and more importantly, maintainable asynchronous code).

However, the language difference is going away. The next revision of JavaScript will introduce generators, which (along with a helper library) will make asynchronous code in Node.js just as natural as it is today using async/await. If you want to play with the "coming soon" stuff now, generators were added in V8 3.19, which was rolled into Node.js 0.11.2 (the Unstable branch). Pass --harmony or --harmony-generators to explicitly enable the generator support.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • "...code awaits, it could possibly resume on a different thread, and it's up to you to avoid things like thread-local state..." isn't async/await also ends on same thread as caller method on UI thread? – PKV Jan 24 '14 at 20:30
  • 12
    @PKV: There is no UI thread on ASP.NET. There is a *request context*, which an ASP.NET thread pool thread *enters* when it works on a request. The thread then *exits* the request context when it returns to the thread pool. `await` will ensure the method resumes on the same *request context*, not the same *thread*. – Stephen Cleary Jan 24 '14 at 20:52
  • 1
    `you'll still end up with really awkward code when you do anything non-trivial`. I can't agree with that. Using a library like async.js you can write extremely elegant async code in JS, even when very complex. – UpTheCreek Nov 26 '14 at 18:49
  • 3
    @UpTheCreek: Various libraries have various techniques/solutions; depends on exactly what you want to do. The complexity comes in from splitting up what should be the actual logic into multiple continuations. This is the complexity that generators solve. – Stephen Cleary Nov 26 '14 at 19:03
  • So, basically .NET is much better... why did NodeJS have so much hype, I feel like it's juts because of the massive JS "programmers" who were so happy to be able to use that horrible language (albeit improving now) on the server side too. – SpaceMonkey Mar 10 '16 at 21:34
  • 1
    @Spacemonkey: I wouldn't say "better". Different, certainly. More tuned for high-performance servers, absolutely. But it's harder to write correct multi-threaded code than it is to write correct single-threaded code. So, (once JS gets `async` officially) I think Node will be easier to write. And who knows, maybe eventually it'll win due to that. JS is getting some *nice* (and much needed) improvements, including `async`. – Stephen Cleary Mar 10 '16 at 21:43
  • @StephenCleary btw, if you are interested in writing safe single-threaded scalable server code, have a look at Orleans project on Github. It's still not very mature, but it's very promising. It's an actor system like Akka. – SpaceMonkey Mar 10 '16 at 21:47
20

The difference between Node.js's async model and C#'s async/await model is huge. The async model that has Node.js is similar to the old async model in C# and .Net called Event-based Asynchronous Pattern (EAP). C# and .Net has 3 async models, you can read about them at Asynchronous Programming Patterns. The most modern async model in C# is Task-based with C#'s async and await keywords, you can read about it at Task-based Asynchronous Pattern. The C#'s async/await keywords make asynchronous code linear and let you avoid "Callback Hell" much better than in any of other programming languages. You need just try it, and after that you will never do it in other way. You just write code consuming asynchronous operations and don't worry about readability because it looks like you write any other code. Please, watch this videos:

  1. Async programming deep dive
  2. Async in ASP.NET
  3. Understanding async and Awaitable Tasks
And please, try to do something asynchronous in both C# and then Node.js to compare. You will see the difference.

EDIT: Since Node.js V8 JavaScript engine supports generators, defined in ECMAScript 6 Draft, "Callback Hell" in JavaScript code also can be easily avoided. It brings some form of async/await to life in JavaScript

6

With nodejs, all requests go in the event queue. Node's event loop uses a single thread to process items in the event queue, doing all non-IO work, and sending to C++ threadpool (using javascript callbacks to manage asynchrony) all IO-bound work. The C++ threads then add to the event queue its results.

The differences with ASP.NET (the two first apply pretty much to all web servers that allow async IO) is that :

  1. ASP.NET uses a different thread for each incoming requests, so you get an overhead of context switching
  2. .NET doesn't force you to use async to do IO-bound work, so it isn't as idiomatic as nodejs where IO-bound api calls are de facto async (with callbacks)
  3. .NET' "await-async" add's a step at compile time to add "callbacks", so you can write linear code (no callback function passing), in contrast with nodejs

There are so much places on the web that describe node's architecture, but here's something : http://johanndutoit.net/presentations/2013/02/gdg-capetown-nodejs-workshop-23-feb-2013/index.html#1

billy
  • 1,165
  • 9
  • 23
  • hey, i got the point you making here. So should understand it like, for 'n' incoming requests: ASP.Net making 'n' threads and nodejs also creating 'n' threads if all n request requires I/O? – PKV Jan 24 '14 at 20:42
  • 4
    @PKV: [Threads are not required for asynchronous I/O](http://blog.stephencleary.com/2013/11/there-is-no-thread.html), as I describe on my blog. This is true for both Node.js and `async` ASP.NET. – Stephen Cleary Jan 24 '14 at 20:53
  • 2
    @billy: Yes, but it only uses it for situations where there *should* be an asynchronous API but it was overlooked and there is only a synchronous API. In this case, Node.js will wrap the synchronous API in a thread pool thread (which is blocked while the OS performs the actual operation asynchronously). So the Node.js threadpool is a workaround for incomplete APIs; it's not *normally* used for asynchronous I/O. – Stephen Cleary Jan 27 '14 at 15:20
3

The difference between async in Nodejs and .NET is in using preemptive multitasking for user code. .NET uses preemptive multitasking for user code, and Nodejs does not.

Nodejs uses an internal thread pool for serving IO requests, and a single thread for executing your JS code, including IO callbacks.

One of the consequences of using preemptive multitasking (.NET) is that a shared state can be altered by another stack of execution while executing a stack. That is not the case in Nodejs - no callback from an async operation can run simultaneously with currently executing stack. Another stacks of execution just do not exist in Javascript. A result of an async operation would be available to the callbacks only when current stack of execution exits completely. Having that, simple while(true); hangs Nodejs, because in this case current stack does not exit and the next loop is never initiated.

To understand the difference consider the two examples, one for js an one for net. var p = new Promise(function(resolve) { setTimeout(resolve, 500, "my content"); }); p.then(function(value) { // ... value === "my content"

In this code, you can safely put a handler (then) after you "started" an async operation, because you can be sure, that no callback code that is initiated by an async operation would ever execute until the entire current call stack exits. The callbacks are handled in next cycles. As for the timer callbacks, they are treated the same. Async timer event justs puts callback processing on queue to be processed in a following cycle.

In .NET it's different. There are no cycles. There is preemptive multitasking.

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");
}

Here is a Hello World .NET a-la Nodejs code to demonstrate async processing on single thread and using a thread pool for async IO, just like node does. (.NET includes TPL and IAsyncResult versions of async IO operations, but there's no difference for the purposes of this example. Anyway everything ends up with different threads on a thread pool.)

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>();
}
George Polevoy
  • 7,450
  • 3
  • 36
  • 61