如何将 C# Parallel.For 与线程本地存储引用类型一起使用
How to use C# Parallel.For with thread local storage reference type
我正在寻找有关如何在 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 行,就在“(200),”之后:
"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);
}
我发现了另一个有点类似的问题 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 并指定 return 值时,它给出了其他错误:
sb1.Append(str1.ElementAt(i)).Append(' ').Append(str2.ElementAt(i)).Append(' ').Append(str3.ElementAt(i));
此行现在生成错误:
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
所以,我不知道问题出在哪里。
原来让代码编译正确的问题是语法问题。如果微软为这种情况发布了一个示例,那真的会有所帮助。以下代码将构建并 运行 正确:
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 一个值。因为我没有使用 return 值,所以我没有输入 - 至少一开始是这样。当我输入 return 语句时,编译器显示 "ElementAt" 静态方法的另一个错误 - 如上文 EDIT.
所示
事实证明,编译器标记为问题的 "ElementAt" 错误与问题完全无关。这让我想起了我在 C++ 的日子,当时编译器的帮助还不如 C# 编译器。将错误的行识别为错误在 C# 中很少见 - 但从这个例子可以看出,它确实发生了。
第二个问题是行 (x) => 0)。这一行是函数中的第 5 个参数,在所有工作完成后由每个线程调用。我最初尝试将其更改为 (x) => x.Clear。这最终生成了错误消息:
Only assignment, call, increment, decrement, await, and new object
expressions can be used as a statement
"ElementAt" 错误仍然存在。因此,根据这个线索,我决定 (x) => 0 可能是导致真正问题的原因 - 减去错误消息。由于此时工作已完成,我将其更改为将 StringBuffer 对象设置为 null,因为不再需要它。神奇的是,所有 "ElementAt" 错误都消失了。之后它构建并 运行 正确。
Parallel.For 提供了一些不错的功能,但我认为最好建议 Microsoft 重新访问其中的一些功能。任何时候线路导致问题,都应该被标记为问题。这至少需要解决。
如果 Microsoft 可以为 Parallel.For 提供一些额外的覆盖方法,允许 void 被 returned,并接受第 5 个参数的 null 值,那也很好。我实际上尝试为此发送一个 NULL 值,然后它就建立了。但是,因此出现了运行次异常。更好的想法是在不需要调用 "thread completion" 方法时为 4 个参数提供覆盖。
这是您自己的 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);
}
您也可以通过使用 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();
这可能不是那么快,但可能要简单得多。
我正在寻找有关如何在 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 行,就在“(200),”之后:
"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);
}
我发现了另一个有点类似的问题 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 并指定 return 值时,它给出了其他错误:
sb1.Append(str1.ElementAt(i)).Append(' ').Append(str2.ElementAt(i)).Append(' ').Append(str3.ElementAt(i));
此行现在生成错误:
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
所以,我不知道问题出在哪里。
原来让代码编译正确的问题是语法问题。如果微软为这种情况发布了一个示例,那真的会有所帮助。以下代码将构建并 运行 正确:
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 一个值。因为我没有使用 return 值,所以我没有输入 - 至少一开始是这样。当我输入 return 语句时,编译器显示 "ElementAt" 静态方法的另一个错误 - 如上文 EDIT.
所示事实证明,编译器标记为问题的 "ElementAt" 错误与问题完全无关。这让我想起了我在 C++ 的日子,当时编译器的帮助还不如 C# 编译器。将错误的行识别为错误在 C# 中很少见 - 但从这个例子可以看出,它确实发生了。
第二个问题是行 (x) => 0)。这一行是函数中的第 5 个参数,在所有工作完成后由每个线程调用。我最初尝试将其更改为 (x) => x.Clear。这最终生成了错误消息:
Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
"ElementAt" 错误仍然存在。因此,根据这个线索,我决定 (x) => 0 可能是导致真正问题的原因 - 减去错误消息。由于此时工作已完成,我将其更改为将 StringBuffer 对象设置为 null,因为不再需要它。神奇的是,所有 "ElementAt" 错误都消失了。之后它构建并 运行 正确。
Parallel.For 提供了一些不错的功能,但我认为最好建议 Microsoft 重新访问其中的一些功能。任何时候线路导致问题,都应该被标记为问题。这至少需要解决。
如果 Microsoft 可以为 Parallel.For 提供一些额外的覆盖方法,允许 void 被 returned,并接受第 5 个参数的 null 值,那也很好。我实际上尝试为此发送一个 NULL 值,然后它就建立了。但是,因此出现了运行次异常。更好的想法是在不需要调用 "thread completion" 方法时为 4 个参数提供覆盖。
这是您自己的 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);
}
您也可以通过使用 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();
这可能不是那么快,但可能要简单得多。