C# 奇怪的多线程索引越界问题
无论我使用什么:线程类或基于TPL任务的模式。数据上总是有一个超出界限的索引。 通过进一步研究,我发现计数器I的值可以是4,这甚至不可能。 我错过了什么?我期待你的专家意见 使用Visual Studio 15.8(2017)16.1(2019)进行测试,项目目标为.NET framework 4.72C# 奇怪的多线程索引越界问题,c#,multithreading,C#,Multithreading,无论我使用什么:线程类或基于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.Ta
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();
}
}
}
使用系统;
使用System.Collections.Generic;
使用系统线程;
使用System.Threading.Tasks;
名称空间控制台EAPP1
{
班级计划
{
静态void Main(字符串[]参数)
{
//一个多线程搜索演示,省去了很多代码,简单明了
//生成0-99个元素,按升序总共生成100个元素
List testData=new List();
对于(int i=0;i<100;i++)
{
testData.Add(i);
}
List searchFor=新列表(){
67, 0, 99,
23, 24, 25,
-1, 106
};
常数int threadscont=4;
//测试开关
bool usetreadinsteadoftasktpl=true;
如果(使用线程代替tasktpl)
{
//搜索每一条数据
for(int j=0;j{
//捕获计数器以确保没有lambda陷阱
int counterI=i;
int计数器j=j;
WriteLine(“i值:{0}”,counterI);
WriteLine(“j值:{0}”,counterJ);
//你的代码
});
螺纹[i]=螺纹;
线程[i].Start();
}
对于(int i=0;i{
//捕获计数器以确保没有lambda陷阱
int counterI=i;
int计数器j=j;
WriteLine(“i值:{0}”,counterI);
WriteLine(“j值:{0}”,counterJ);
//你的代码
},新的CancellationTokenSource()。令牌,
TaskCreationOptions.None,TaskScheduler.Default);
任务[i]=任务;
}
Task.WaitAll(任务);
Console.WriteLine();
}
}
Console.ReadKey();
}
}
}
i的期望值应该经过0…3,
但i的实际值可能等于4或在迭代之间保持不变。在循环开始时(不在lambda内),应重新分配
i
和j
:
for(int i=0;i
{
WriteLine(“i值:{0}”,counterI);
WriteLine(“j值:{0}”,counterJ);
//你的代码
}
}
您的线程计划执行(在调用Start()
后不会立即启动),并且当它开始运行时,i
(和j
)的值可能已经更改。(您可以查看编译器生成的代码,了解本例和您的代码)
任务也是如此——它们是计划好的,不是立即启动的
更多详情:
请参阅(操作
使用委托而不是线程
)和生成的代码
您可以看到差异(生成的代码创建类的实例)
存储要打印的值和实际打印的方法):
- 在委托内部重新分配-对于每次迭代,使用相同的实例,并在调用委托后增加值。使用
时,它会按预期工作, 因为它立即执行(从生成的类调用方法 若要打印值),则生成的类的值将递增并新建 迭代开始了操作
- 重新分配外部委托-已创建生成类的实例 对于每个迭代,没有增量。每个迭代都有 独立实例和下一次迭代无法更改的值 上一个
i
variable-一些
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
}
}