Task.Factory.StartNew与Parallel.Invoke [英] Task.Factory.StartNew vs. Parallel.Invoke

查看:124
本文介绍了Task.Factory.StartNew与Parallel.Invoke的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用程序从几个几十个执行耦合并行数百个动作(行动,没有返回值)。

In my application I execute from couple of dozens to couple of hundreds actions in parallel (no return value for the actions).

哪种办法最优化


  1. 使用 Task.Factory.StartNew 在foreach循环迭代动作阵列(动作[]

  1. Using Task.Factory.StartNew in foreach loop iterating over Action array (Action[])

Task.Factory.StartNew(()=> someAction());

使用平行类,其中动作动作阵列(行动[]

Using Parallel class where actions is Action array (Action[])

Parallel.Invoke(动作);

那两个方法相同呢?是否有任何性能影响?

Are those two approaches equivalent? Are there any performance implications?

修改

我已经进行了一些性能测试和我的机器上(2个CPU核心2个)的结果似乎是非常相似的。我不知道它是如何将看起来像在其他机器上像1个CPU。另外,我不知道(不知道如何测试它非常准确的方法)什么是内存消耗。

I have performed some performance tests and on my machine (2 CPU 2 Cores each) results seems to be very similar. I am not sure how it is going to look like on other machines like 1 CPU. Also I am not sure (do not know how to test it very accurate way) what is memory consumption.

推荐答案

最重要这两者之间的区别是, Parallel.Invoke 将等待所有动作的代码继续之前完成,而 StartNew 将移动到下一行代码,让任务自己的好时间才能完成。

The most important difference between these two is that Parallel.Invoke will wait for all the actions to complete before continuing with the code, whereas StartNew will move on to the next line of code, allowing the tasks to complete in their own good time.

这语义差别应该是你的第一个(可能只)的考虑。但参考,这里有一个风向标:

This semantic difference should be your first (and probably only) consideration. But for informational purposes, here's a benchmark:

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var actions2 =
    (from i in Enumerable.Range(1, 10000)
    select (Action)(() => {})).ToArray();

    var awaitList = new Task[actions2.Length];
    var actions = new[]
    {
        new TimedAction("Task.Factory.StartNew", () =>
        {
            // Enter code to test here
            int j = 0;
            foreach(var action in actions2)
            {
                awaitList[j++] = Task.Factory.StartNew(action);
            }
            Task.WaitAll(awaitList);
        }),
        new TimedAction("Parallel.Invoke", () =>
        {
            // Enter code to test here
            Parallel.Invoke(actions2);
        }),
    };
    const int TimesToRun = 100; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for(int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult{Message = action.Message};
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for(int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message {get;set;}
    public double DryRun1 {get;set;}
    public double DryRun2 {get;set;}
    public double FullRun1 {get;set;}
    public double FullRun2 {get;set;}
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message {get;private set;}
    public Action Action {get;private set;}
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion



结果:

Results:

Message               | DryRun1 | DryRun2 | FullRun1 | FullRun2
----------------------------------------------------------------
Task.Factory.StartNew | 43.0592 | 50.847  | 452.2637 | 463.2310
Parallel.Invoke       | 10.5717 |  9.948  | 102.7767 | 101.1158 



正如你可以看到,使用Parallel.Invoke大致可以不必等待了一堆快4.5倍的newed行动任务来完成。当然,这时候你的行动什么都不做。更多的每个动作呢,你就越会发现有差别的。

As you can see, using Parallel.Invoke can be roughly 4.5x faster than waiting for a bunch of newed-up tasks to complete. Of course, that's when your actions do absolutely nothing. The more each action does, the less of a difference you'll notice.

这篇关于Task.Factory.StartNew与Parallel.Invoke的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆