无论我使用什么:基于线程类或TPL任务的模式。数据上总会有一个索引超出范围。 通过进一步的研究,我发现计数器i的值为4,这甚至是不可能的。 我错过了什么?我期待您的专家意见!
在Visual Studio 15.8(2017)16.1(2019)中进行了测试,该项目针对.NET Framework 4.72。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
// a multi-threading search demo, omit much code for simple and clear
// generate 0-99, total 100 elements with ascending order
List<int> testData = new List<int>();
for (int i = 0; i < 100; i++)
{
testData.Add(i);
}
List<int> searchFor = new List<int>() {
67, 0, 99,
23, 24, 25,
-1, 106
};
const int threadsCount = 4;
// Test switch
bool useThreadInsteadOfTaskTPL = true;
if (useThreadInsteadOfTaskTPL)
{
// search every piece of data
for (int j = 0; j < searchFor.Count; j++)
{
Thread[] threads = new Thread[threadsCount];
Console.WriteLine("Search for: {0}", searchFor[j]);
// trying to divide the data into 4 parts, and search in parallel
for (int i = 0; i < threadsCount; i++)
{
Thread thread = new Thread(() => {
// Capture the counters to make sure no lambda pitfall
int counterI = i;
int counterJ = j;
Console.WriteLine("i value: {0}", counterI);
Console.WriteLine("j value: {0}", counterJ);
// your code
});
threads[i] = thread;
threads[i].Start();
}
for (int i = 0; i < threads.Length; i++)
{
threads[i].Join();
}
Console.WriteLine();
}
}
else
{
for (int j = 0; j < searchFor.Count; j++)
{
Task[] tasks = new Task[threadsCount];
Console.WriteLine("Search for: {0}", searchFor[j]);
// trying to divide the data into 4 parts, and search in parallel
for (int i = 0; i < threadsCount; i++)
{
Task task = Task.Factory.StartNew(() => {
// Capture the counters to make sure no lambda pitfall
int counterI = i;
int counterJ = j;
Console.WriteLine("i value: {0}", counterI);
Console.WriteLine("j value: {0}", counterJ);
// your code
}, new CancellationTokenSource().Token,
TaskCreationOptions.None, TaskScheduler.Default);
tasks[i] = task;
}
Task.WaitAll(tasks);
Console.WriteLine();
}
}
Console.ReadKey();
}
}
}
i的期望值应经过0 ... 3, 但是i的实际值可能等于4或在两次迭代之间保持不变。
答案 0 :(得分:7)
您应在循环开始时(而不是在lambda内部)重新分配i
和j
:
for (int i = 0; i < threadsCount; i++)
{
// Capture the counters to make sure no lambda pitfall
int counterI = i;
int counterJ = j;
Thread thread = new Thread(() =>
{
Console.WriteLine("i value: {0}", counterI);
Console.WriteLine("j value: {0}", counterJ);
// your code
}
}
您的线程被安排执行(它不会在调用Start()
之后立即启动),并且当它开始运行时,i
(和j
)的值可以被更改。 (您可以查看针对这种情况以及您自己的情况的编译器生成的代码。)
与任务相同-它们是计划好的,不是立即开始的。
更多详细信息:
请参见this example(使用Action
委托代替Thread
)和生成的代码。
您可以看到差异(生成的代码创建类的实例 存储要打印的值和实际打印的方法):
Action
可以按预期运行,
因为它立即执行(从生成的类中调用方法
打印值),然后将生成的类的值增加并新建
迭代开始。对于线程而言,唯一的区别是线程不会立即启动,而是计划执行时间,这需要一些时间。对于第一种情况-调用用于打印值的方法时,该值可能已经增加(因为所有迭代的实例相同),您会得到意外的结果。
您可以通过多次运行应用程序来检查此情况(第一种情况)-打印i
变量时不会得到完全相同的结果-有时在不期望的情况下会递增(因为调用花费了一些时间) Start()
并在调度后实际开始执行线程),有时值是正确的(因为在增量发生之前,线程是在调用Start()
之后几乎立即调度并启动的)。