我正在寻找一个关于如何在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;有一些无效的论点
所以,我不知道问题是什么。
答案 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();
这可能不会那么快,但可能要简单得多。