无论我使用什么:线程类或基于 TPL 任务的模式。数据上总是存在超出范围的索引。 通过进一步的研究,我发现 counter 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 或在迭代之间保持不变。
最佳答案
您应该在循环开始时重新分配 i
和 j
(不在 lambda 内部):
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
委托(delegate)代替 Thread
)和生成的代码。
你可以看到差异(生成的代码创建类的实例 存储要打印的值和实际打印的方法):
- 在委托(delegate)内部重新分配 - 每次迭代都使用相同的实例,并且在调用委托(delegate)后增加值。使用
Action
它按预期工作, 因为它会立即执行(从生成的类调用方法 打印值),然后生成类的值递增和新的 迭代开始。 - reassign outside delegate - 创建生成类的实例 对于每次迭代,因此没有增量。每次迭代都有 独立实例和下一次迭代不能改变值 上一个。
在线程的情况下,唯一的区别是线程不会立即启动,它会被安排执行,这需要一些时间。对于第一种情况 - 当调用打印值的方法时,该值可能已经递增(因为所有迭代的实例相同)并且您会得到意想不到的结果。
您可以通过多次运行应用程序来检查这一点(对于第一种情况)-打印 i
变量时您将得到不同的结果-有时它会在不期望的情况下递增(因为它花了一些时间从调用 Start()
到调度后线程执行的实际开始的时间),有时值是正确的(因为线程在增量之前调用 Start()
后几乎立即被调度和启动发生)。
关于c# - 奇怪的多线程索引越界问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56766689/