如何使用C#Parallel.For与线程本地存储引用类型

时间:2015-03-10 08:09:55

标签: c# linq parallel-processing task-parallel-library

我正在寻找一个关于如何在C#中使用带引用类型的Parallel.For的示例。我已经阅读了MSDN文档,我能找到的所有内容都是使用值类型进行线程本地存储的示例。我尝试的代码如下:

public string[] BuildStrings(IEnumerable<string> str1, IEnumerable<string> str2, IEnumerable<string> str3)
{
    // This method aggregates the strings in each of the collections and returns the combined set of strings.  For example:
    // str1 = "A1", "B1", "C1"
    // str2 = "A2", "B2", "C2"
    // str3 = "A3", "B3", "C3"
    //
    // Should return:
    // "A1 A2 A3"
    // "B1 B2 B3"
    // "C1 C2 C3"
    //
    // The idea behind this code is to use a Parallel.For along with a thread local storage StringBuilder object per thread.
    // Don't need any final method to execute after each partition has completed.
    // No example on how to do this that I can find.

    int StrCount = str1.Count(); // str1, str2, and str3 guaranteed to be equal in size and > 0.
    var RetStr = new string[StrCount];
    Parallel.For<StringBuilder>(0, StrCount, () => new StringBuilder(200), (i, j, sb1) =>
    {
        sb1.Clear();
        sb1.Append(str1.ElementAt(i)).Append(' ').Append(str2.ElementAt(i)).Append(' ').Append(str3.ElementAt(i));
        RetStr[i] = sb1.ToString();
    }, (x) => 0);
    return RetStr;
}

此代码无法在Visual Studio 2013 Express版本上编译。错误发生在Parallel.For行上,紧跟在&#34;(200)之后,&#34;:

  

&#34;并非所有代码路径都返回type的lambda表达式中的值   &#39;&System.Func LT;   INT,System.Threading.Tasks.ParallelLoopState,System.Text.StringBuilder,System.Text.StringBuilder&GT;&#39;&#34;

测试代码如下所示:

static void Main(string[] args)
{
    int Loop;
    const int ArrSize = 50000;
    // Declare the lists to hold the first, middle, and last names of the clients.
    List<string> List1 = new List<string>(ArrSize);
    List<string> List2 = new List<string>(ArrSize);
    List<string> List3 = new List<string>(ArrSize);
    // Init the data.
    for (Loop = 0; Loop < ArrSize; Loop++)
    {
       List1.Add((Loop + 10000000).ToString());
       List2.Add((Loop + 10100000).ToString());
       List3.Add((Loop + 1100000).ToString());
    }
    IEnumerable<string> FN = List1;
    IEnumerable<string> MN = List2;
    IEnumerable<string> LN = List3;
    //
    // Time running the Parallel.For version.
    //
    Stopwatch SW = new Stopwatch();
    SW.Start();
    string[] RetStrings;
    RetStrings = BuildMatchArrayOld(FN, MN, LN);
    // Get the elapsed time as a TimeSpan value.
    SW.Stop();
    TimeSpan TS = SW.Elapsed;
    // Format and display the TimeSpan value. 
    string ElapsedTime = TS.TotalSeconds.ToString();
    Console.WriteLine("Old  RunTime = " + ElapsedTime);
}

我发现另一个有点类似的问题here也没有编译。但是,使用更简单形式的函数所接受的答案对我没有帮助。我可以为这个特殊情况做到这一点,但是我真的想知道如何在将来使用带引用类型的线程本地存储。这是一个MS错误,还是我错过了正确的语法?

修改

我确实尝试过此link的代码:

static void Main()
{
    int[] nums = Enumerable.Range(0, 1000000).ToArray();
    long total = 0;

    // Use type parameter to make subtotal a long, not an int
    Parallel.For<long>(0, nums.Length, () => 0, (j, loop, subtotal) =>
    {
        subtotal += nums[j];
        return subtotal;
    },
        (x) => Interlocked.Add(ref total, x)
    );

    Console.WriteLine("The total is {0:N0}", total);
    Console.WriteLine("Press any key to exit");
    Console.ReadKey();
}

似乎工作正常。

问题是,当我尝试在我的代码中使用Parallel.For并指定返回值时,它会产生其他错误:

sb1.Append(str1.ElementAt(i)).Append(' ').Append(str2.ElementAt(i)).Append(' ').Append(str3.ElementAt(i));

此行现在会生成错误:

  

错误&#39; System.Collections.Generic.IEnumerable&#39;才不是   包含&#39; ElementAt&#39;的定义和最好的扩展方法   超载   &#39; System.Linq.Enumerable.ElementAt(System.Collections.Generic.IEnumerable,   INT)&#39;有一些无效的论点

所以,我不知道问题是什么。

3 个答案:

答案 0 :(得分:1)

事实证明,使代码正确编译的问题是语法问题。如果微软就这个案例发布了一个例子,那真的会有所帮助。以下代码将构建并正确运行:

public string[] BuildStrings(IEnumerable<string> str1, IEnumerable<string> str2, IEnumerable<string> str3)
{
    // This method aggregates the strings in each of the collections and returns the combined set of strings.  For example:
    // str1 = "A1", "B1", "C1"
    // str2 = "A2", "B2", "C2"
    // str3 = "A3", "B3", "C3"
    //
    // Should return:
    // "A1 A2 A3"
    // "B1 B2 B3"
    // "C1 C2 C3"
    //
    // The idea behind this code is to use a Parallel.For along with a thread local storage StringBuilder object per thread.
    // Don't need any final method to execute after each partition has completed.
    // No example on how to do this that I can find.

    int StrCount = str1.Count(); // str1, str2, and str3 guaranteed to be equal in size and > 0.
    var RetStr = new string[StrCount];
    Parallel.For<StringBuilder>(0, StrCount, () => new StringBuilder(200), (i, j, sb1) =>
    {
        sb1.Clear();
        sb1.Append(str1.ElementAt(i)).Append(' ').Append(str2.ElementAt(i)).Append(' ').Append(str3.ElementAt(i));
        RetStr[i] = sb1.ToString();
        return sb1; // Problem #1 solved.  Signature of function requires return value.
    }, (x) => x = null); // Problem #2 solved.  Replaces (x) => 0 above.
    return RetStr;
}

因此,正如Jon Skeet在评论中指出的那样,第一个问题是我的lambda方法未能返回值。由于我没有使用返回值,所以我没有放入 - 至少最初。当我输入return语句时,编译器显示“ElementAt”静态方法的另一个错误 - 如上面 EDIT 所示。

事实证明,编译器标记为问题的“ElementAt”错误与该问题没有任何关系。这往往让我想起了我的C ++时代,当时编译器没有C#编译器那么有用。在C#中将错误的行识别为错误是非常罕见的 - 但从这个例子中可以看出,它确实发生了。

第二个问题是第(x)行=&gt; 0)。该行是函数中的第5个参数,在完成所有工作后由每个线程调用。我最初尝试将其更改为(x)=&gt; x.Clear。这最终导致生成错误消息:

  

仅分配,调用,增量,减量,等待和新对象   表达式可以用作语句

“ElementAt”错误仍然存​​在。所以,从这个线索我决定(x)=&gt; 0可能导致真正的问题 - 减去错误消息。由于此时工作已完成,我更改了它以将StringBuffer对象设置为null,因为它不再需要。可悲的是,所有“ElementAt”错误都消失了。之后它就构建并运行正确。

Parallel.For提供了一些不错的功能,但我认为微软建议重新考虑一些功能。任何时候一行导致问题,它应该被标记为这样。这至少需要解决。

如果Microsoft可以为Parallel.For提供一些额外的覆盖方法,这将允许返回void,并接受第5个参数的空值,这也是很好的。我实际上尝试发送一个NULL值,并建立它。但是,由于这个原因,发生了运行时异常。更好的想法是在不需要调用“线程完成”方法时为4个参数提供覆盖。

答案 1 :(得分:1)

这是您自己For重载的样子

    public static ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body)
    {
        return Parallel.For(fromInclusive, toExclusive, localInit, body, localFinally: _ => { });
    }

        static void StringBuilderFor(int count, Action<int, ParallelLoopState, StringBuilder> body)
    {
        Func<int, ParallelLoopState, StringBuilder, StringBuilder> b = (i, j, sb1) => { body(i, j, sb1); return sb1; };
        For(0, count, () => new StringBuilder(200), b);
    }

答案 2 :(得分:1)

您还可以使用LINQ和AsParallel()而不是显式并行来避免整个问题。

        int StrCount = str1.Count(); // str1, str2, and str3 guaranteed to be equal in size and > 0.
        var RetStr = from i in Enumerable.Range(0, StrCount)
                     let sb1 = new StringBuilder(200)
                     select (sb1.Append(str1.ElementAt(i)).Append(' ').Append(str2.ElementAt(i)).Append(' ').Append(str3.ElementAt(i))).ToString();
        return RetStr.AsParallel().ToArray();

这可能不会那么快,但可能要简单得多。