Brad
Brad

Reputation: 163262

.NET threading like Node.js/V8?

I've been away from .NET desktop programming for some time, while drinking the Node.js koolaid. There are some parts of Node.js I find easy to work with. In particular, I like the simplicity of the threading model, and that I can have a few of the benefits of a multithreaded application while only writing code to keep track of a single thread.

Now, I have a need to write a multi-threaded application in .NET, and it occurred to me that there is no reason I cannot use a similar threading model that is used to build Node.js applications. In particular, I want to:

Does such a framework for this threading model already exist within, or for .NET applications? If not, are there parts of .NET that already support or handle some of the functionality that I am seeking?

Upvotes: 5

Views: 909

Answers (4)

Amit Mittal
Amit Mittal

Reputation: 2736

It can be achieved, among other options, by taking advantage of Window's native event loop.

Following code is a POC for the same and it addresses all the 3 points you have mentioned. But note that it is just a POC. It is not type safe and it uses Delegate.DynamicInvoke which can be slow but it proves the concept nevertheless.

public static class EventLoop
{
    private class EventTask
    {
        public EventTask(Delegate taskHandler) : this(taskHandler, null) {}

        public EventTask(Delegate taskHandler, Delegate callback)
        {
            TaskHandler = taskHandler;
            Callback = callback;
        }

        private Delegate Callback {get; set;}

        private Delegate TaskHandler {get; set;}

        public void Invoke(object param)
        {
            object[] paramArr = null;
            if (param.GetType().Equals(typeof(object[])))
            {
                paramArr = (object[]) param; //So that DynamicInvoke does not complain
            }

            object res = null;

            if (TaskHandler != null)
            {
                if (paramArr != null)
                {
                    res = TaskHandler.DynamicInvoke(paramArr);
                }
                else
                {
                    res = TaskHandler.DynamicInvoke(param);
                }
            }

            if (Callback != null)
            {
                EnqueueSyncTask(Callback, res);
            }
        }
    }

    private static WindowsFormsSynchronizationContext _syncContext;
    public static void Run(Action<string[]> mainProc, string[] args)
    {
        //You need to reference System.Windows.Forms
        _syncContext = new WindowsFormsSynchronizationContext();
        EnqueueSyncTask(mainProc, args);
        Application.Run();
    }

    public static void EnqueueSyncTask(Delegate taskHandler, object param)
    {
        //All these tasks will run one-by-one in order on Main thread
        //either on call of Application.DoEvenets or when Main thread becomes idle
        _syncContext.Post(new EventTask(taskHandler).Invoke, param);
    }

    public static void EnqueueAsyncTask(Delegate taskHandler, object param, Delegate callback)
    {
       //En-queue on .Net Thread Pool
       ThreadPool.QueueUserWorkItem(new EventTask(taskHandler, callback).Invoke, param);
    }
}

Client Code:

    [STAThread]
    static void Main(string[] args)
    {
        Thread.CurrentThread.Name = "MAIN THREAD";
        Console.WriteLine("Method Main: " + Thread.CurrentThread.Name);
        EventLoop.Run(MainProc, args);
    }

    static void MainProc(string[] args)
    {
        Console.WriteLine("Method MainProc: " + Thread.CurrentThread.Name);
        Console.WriteLine("Queuing Long Running Task...");
        EventLoop.EnqueueAsyncTask(new Func<int,int,int>(LongCalculation), new object[]{5,6}, new Action<int>(PrintResult));
        Console.WriteLine("Queued Long Running Task");

        Thread.Sleep(400); //Do more work
        EventLoop.EnqueueAsyncTask(new Func<int, int, int>(LongCalculation), new object[] { 15, 16 }, new Action<int>(PrintResult));
        Thread.Sleep(150); //Do some more work but within this time 2nd task is not able to complete, meanwhile 1st task completes

        //Long running Tasks will run in background but callback will be executed only when Main thread becomes idle
        //To execute the callbacks before that, call Application.DoEvents
        Application.DoEvents(); //PrintResult for 1st task as 2nd is not yet complete
        Console.WriteLine("Method MainProc: Working over-time!!!!");
        Thread.Sleep(500); //After this sleep, 2nd Task's print will also be called as Main thread will become idle
    }

    static int LongCalculation(int a, int b)
    {
        Console.WriteLine("Method LongCalculation, Is Thread Pool Thread: " + Thread.CurrentThread.IsThreadPoolThread);
        Console.WriteLine("Running Long Calculation");
        Thread.Sleep(500); //long calc
        Console.WriteLine("completed Long Calculation");
        return a + b;
    }

    static void PrintResult(int a)
    {
        Console.WriteLine("Method PrintResult: " + Thread.CurrentThread.Name);
        Console.WriteLine("Result: " + a);
        //Continue processing potentially queuing more long running tasks
    }

Output:

enter image description here

Upvotes: 1

Stephen Cleary
Stephen Cleary

Reputation: 456537

As others have mentioned, async / await is an excellent choice for .NET. In particular:

  • Task / Task<T> / TaskCompletionSource<T> are analogous to JavaScript's Deferred / Promise / Future.
  • It's pretty easy to create JavaScript-style continuations using .NET-style continuations, but for the most part you won't need them.
  • There is no JavaScript equivalent to async / await. async allows you to write your methods as though they were synchronous, and under the hood it breaks them up into continuations wherever there's an await. So you don't have to use continuation passing style.
  • For operations on a background thread, your best choice is Task.Run. However, the standard pattern for .NET is to have the background operation compute and return a single value, instead of having continuous bidirectional messaging with the main thread.
  • If you do need a "stream" of asynchronous data, you should use TPL Dataflow or Rx. This is where things diverge from JS quite a bit.

I recommend you start with my async / await intro post.

Upvotes: 3

Nigel Findlater
Nigel Findlater

Reputation: 1724

I would recommend the TPL. Here’s an example of how it works

Void Work()
{
    Task<string> ts = Get();
    ts.ContinueWith(t =>
        {
        string result = t.Result;
        Console.WriteLine(result);
        });
}

There are a whole range of possibilities for cancelation, error handling using different schedulers etc. With .Net 4.5 you have the possibility of using await

async void Work()
{ 
    Task<string> ts = Get(); 
    string result = await ts; 
    Console.WriteLine(result); 
}

Here the compiler looks at methods marked async and adds a whole pile of thread safe robust task synchronizing code while leaving the code readable.

Upvotes: 2

Richard Schneider
Richard Schneider

Reputation: 35477

I recommend a look at TPL (Task Parallel Library) which became available in .Net 4.0. It can do points 1 and 2 but not 3.

See http://msdn.microsoft.com/en-us/library/dd460717.aspx

Upvotes: 1

Related Questions