问题
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:
- Call long-running functions with callback parameters. (That function would execute on a thread from a pool. Maybe a simple wrapper function to call functions on new threads would be sufficient?)
- Have those callback function calls ran on the "main" thread for processing
- Maintain automatic synchronization for all objects accessed by this "main" thread, so locking isn't an issue
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?
回答1:
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.
回答2:
As others have mentioned, async
/ await
is an excellent choice for .NET. In particular:
Task
/Task<T>
/TaskCompletionSource<T>
are analogous to JavaScript'sDeferred
/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 anawait
. 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.
回答3:
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
回答4:
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:

来源:https://stackoverflow.com/questions/12574825/net-threading-like-node-js-v8