如何在线程本地存储引用类型中使用C#Parallel.For [英] How to use C# Parallel.For with thread local storage reference type

查看:104
本文介绍了如何在线程本地存储引用类型中使用C#Parallel.For的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找有关如何在带有引用类型的C#中使用Parallel.For的示例.我已经遍历了MSDN文档,而我所能找到的都是将值类型用于线程本地存储的示例.我正在尝试的代码如下:

I am looking for an example on how to use Parallel.For in C# with a reference type. I have been through the MSDN documentation, and all that I can find are examples that use a value type for thread local storage. The code that I'm trying is as follows:

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行上,紧接(200)"之后:

This code will not compile on Visual Studio 2013 Express edition. The error is on the Parallel.For line, right after the "(200),":

并非所有代码路径都在类型为lambda的表达式中返回值 'System.Func< int,System.Threading.Tasks.ParallelLoopState,System.Text.StringBuilder,System.Text.StringBuilder>'"

"Not all code paths return a value in lambda expression of type 'System.Func< int,System.Threading.Tasks.ParallelLoopState,System.Text.StringBuilder,System.Text.StringBuilder>'"

测试代码如下:

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

我在这里也不会编译.但是,使用函数的更简单形式的公认答案对我没有帮助.对于这种特殊情况,我可以这样做,但我真的很想知道将来如何将线程本地存储与引用类型一起使用.这是MS错误,还是我缺少正确的语法?

I found another somewhat similar question here that also does not compile. But, the accepted answer of using a simpler form of the function does not help me here. I could do that for this particular case, but would really like to know how to use thread local storage with a reference type in the future. Is this a MS bug, or am I missing the proper syntax?

编辑

我确实从链接:

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并指定返回值时,会出现其他错误:

The problem is that when I try to use Parallel.For in my code and specify a return value, it gives other errors:

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

此行现在会产生错误:

错误"System.Collections.Generic.IEnumerable"没有 包含"ElementAt"的定义和最佳扩展方法 超载 'System.Linq.Enumerable.ElementAt(System.Collections.Generic.IEnumerable, int)'有一些无效的参数

Error 'System.Collections.Generic.IEnumerable' does not contain a definition for 'ElementAt' and the best extension method overload 'System.Linq.Enumerable.ElementAt(System.Collections.Generic.IEnumerable, int)' has some invalid arguments

所以,我不知道问题出在哪里.

So, I have no clue what the problem is.

推荐答案

事实证明,使代码正确编译的问题是语法问题.如果Microsoft针对此案发布了一个示例,那确实会有所帮助.以下代码将正确构建并运行:

It turns out that the problem with getting the code to compile correctly is a syntax problem. It really would have helped if there had been an example published by Microsoft for this case. The following code will build and run correctly:

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 下所示.

So, the first problem, as was pointed out in the comments by Jon Skeet, was that my lambda method failed to return a value. Since I'm not using a return value, I did not put one in - at least initially. When I put in the return statement, then the compiler showed another error with the "ElementAt" static method - as shown above under EDIT.

事实证明,编译器标记为问题的"ElementAt"错误与该问题完全无关.这往往使我想起了我的C ++时代,当时编译器几乎没有C#编译器有用.在C#中很少将错误的行标识为错误-但是从本示例可以看出,它确实发生了.

It turns out that the "ElementAt" error the compiler flagged as being the problem had nothing at all to do with the issue. This tends to remind me of my C++ days when the compiler was not nearly as helpful as the C# compiler. Identifying the wrong line as an error is quite rare in C# - but as can be seen from this example, it does happen.

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

The second problem was the line (x) => 0). This line is the 5th parameter in the function, and is called by each thread after all its work has been completed. I initially tried changing this to (x) => x.Clear. This ended up generating the error message:

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

Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement

"ElementAt"错误仍然存​​在.因此,根据这个线索,我认为(x)=> 0可能是造成实际问题的原因-减去错误消息.由于工作已经完成,因此我将其更改为将StringBuffer对象设置为null,因为不再需要它.神奇地,所有的"ElementAt"错误都消失了.在此之后,它可以正确构建并运行.

The "ElementAt" errors were still present as well. So, from this clue I decided that the (x) => 0 might be causing the real issue - minus an error message. Since the work is complete at this point, I changed it to set the StringBuffer object to null since it would not be needed again. Magically, all of the "ElementAt" errors vanished. It built and ran correctly after that.

Parallel.For提供了一些不错的功能,但是我认为最好建议Microsoft重新访问其中的一些功能.任何时候任何一行出现问题,都应将其标记为问题.至少需要解决.

Parallel.For provides some nice functionality, but I think Microsoft would be well advised to revisit some of the functionality. Any time a line causes a problem, it should be flagged as such. That at least needs to be addressed.

如果Microsoft可以为Parallel提供一些额外的覆盖方法,那也很好,因为这将允许返回void,并为第5个参数接受null值.实际上,我尝试为此发送NULL值,并且它已构建.但是,因此发生了运行时异常.一个更好的主意是在不需要调用线程完成"方法时提供4个参数的替代.

It would also be nice if Microsoft could provide some additional override methods for Parallel.For that would allow void to be returned, and accepting a null value for the 5th parameter. I actually tried sending in a NULL value for that, and it built. But, a run time exception occurred because of this. A better idea is to provide an override for 4 parameters when no "thread completion" method needs to be called.

这篇关于如何在线程本地存储引用类型中使用C#Parallel.For的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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