问题
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.asyncallows 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