5

我已经离开 .NET 桌面编程有一段时间了,一边喝着 Node.js koolaid。我发现 Node.js 的某些部分很容易使用。特别是,我喜欢线程模型的简单性,并且我可以在只编写代码来跟踪单个线程的同时获得多线程应用程序的一些好处。

现在,我需要在 .NET 中编写一个多线程应用程序,我突然想到,我没有理由不能使用用于构建 Node.js 应用程序的类似线程模型。特别是,我想:

  • 使用回调参数调用长时间运行的函数。(该函数将在池中的线​​程上执行。也许一个简单的包装函数来调用新线程上的函数就足够了?)
  • 让这些回调函数调用在“主”线程上运行以进行处理
  • 维护此“主”线程访问的所有对象的自动同步,因此锁定不是问题

这种线程模型的这种框架是否已经存在于 .NET 应用程序中或用于 .NET 应用程序?如果没有,.NET 的某些部分是否已经支持或处理我正在寻找的某些功能?

4

4 回答 4

3

正如其他人所提到的,async/await是 .NET 的绝佳选择。尤其:

  • Task//类似于JavaScript的// 。Task<T>_TaskCompletionSource<T> DeferredPromiseFuture
  • 使用 .NET 风格的延续创建 JavaScript 风格的延续非常容易,但在大多数情况下,您不需要它们。
  • 没有与async/等效的 JavaScript awaitasync允许您编写方法,就好像它们是同步的一样,并且在引擎盖下,只要有await. 所以你不必使用延续传递风格。
  • 对于后台线程上的操作,最好的选择是Task.Run. 但是,.NET 的标准模式是让后台操作计算并返回单个值,而不是与主线程进行连续的双向消息传递。
  • 如果您确实需要异步数据的“流”,则应使用TPL DataflowRx。这就是事情与 JS 有很大不同的地方。

我建议您从我的async/await介绍文章开始。

于 2012-09-25T06:58:42.460 回答
2

我会推荐 TPL。这是它如何工作的示例

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

取消、使用不同调度程序的错误处理等有多种可能性。使用 .Net 4.5,您可以使用 await

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

在这里,编译器查看标记为 async 的方法,并添加一大堆线程安全、健壮的任务同步代码,同时保持代码可读。

于 2012-09-25T05:56:58.220 回答
1

我推荐看一下在 .Net 4.0 中可用的 TPL(任务并行库)。它可以做到第 1 点和第 2 点,但不能做到第 3 点。

请参阅http://msdn.microsoft.com/en-us/library/dd460717.aspx

于 2012-09-25T01:03:00.337 回答
1

它可以通过利用 Window 的本机事件循环以及其他选项来实现。

以下代码是相同的 POC,它解决了您提到的所有 3 点。但请注意,它只是一个 POC。它不是类型安全的,它使用 Delegate.DynamicInvoke 可能很慢,但它仍然证明了这个概念。

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

客户代码:

    [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
    }

输出:

在此处输入图像描述

于 2012-09-27T12:55:04.717 回答