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