我知道在C#.Net中创建线程的两种方法,你可以使用ThreadStart
委托,而定义lambda表达式也可以完成这项工作。
假设我有一个定义为:
的类class myWorkerClass{
public myWorkerClass(string configInfo) {
}
public void DoWork(ConcurrentQueue<string> executionBuffer) {
}
public void DoWork2() {
}
}
我将在myWorkerClass
函数中构造main
并为每个myWorkerClass
对象构造线程以运行DoWork
方法:
class Program {
static void Main(string args[]) {
int nodeCount = 0;
using (var reader = XmlReader.Create("config.xml"))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "worker")
{
nodeCount++;
}
}
}
myWorkerClass[] obj = new myWorkerClass[nodeCount];
XmlDocument doc = new XmlDocument();
doc.Load("config.xml");
int call = 0;
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
{
// Code to parse xml and get configInfo
obj[call] = new myWorkerClass(configInfo);
call++;
}
ConcurrentQueue<string> bufferExecutions = new ConcurrentQueue<string>();
for (int i = 0; i < call; ++i)
{
//Thread workThread = new Thread(new ThreadStart(obj[i].DoWork2));
Thread workThread = new Thread(() => obj[i].DoWork(bufferExecutions));
workThread.IsBackground = true;
workThread.Start();
}
}
}
我的问题是创建线程的lambda表达方式给了我Index was outside the bounds of the array.
的错误,但是当我使用ThreadStart
委托创建新线程时,我的代码工作正常。我想知道造成我问题的这两种机制之间有什么不同?
答案 0 :(得分:0)
通常,使用Thread
委托开始新ThreadStart
或使用lambda表达式之间没有区别。编译器隐式将lambda表达式转换为委托,并以与Thread
委托相同的方式将其传递给ThreadStart
构造函数。
区别在于您的lambda表达式捕获变量i
。并且(正如@PetSerAl指出的那样)并没有捕获i
的当前值,而是捕获变量i
本身。有关捕获变量的说明,请参阅示例Closing over the loop variable considered harmful。
ThreadStart
变体将在调用 i
构造函数之前(在主线程上)评估new Thread()
的值。但是对于lambda表达式变体,当执行lambda表达式时,将在新创建的线程上计算i
(因此在调用 new Thread()
构造函数之后)。因此,一些线程可能会使用错误的i
值,具体取决于线程实际启动之前执行循环的周期数。最后,如果在循环的最后一个循环之后执行其中一个线程(在最后执行++i
之后),i
将指向obj
数组的最后一个元素,抛出异常。
通过在循环中引入新变量(例如j
) ,并将i
的值复制到其中,可以轻松解决此问题。因为在循环内声明了j
,所以循环的每个循环都会拥有它自己的j
变量,因此它们的值在每个循环中都不会改变,这使得lambda表达式捕获按预期工作。
for (int i = 0; i < call; ++i)
{
// Each cycle of the for loop creates new 'j' variable,
// thus we can safely capture it in lambda expression,
// because it's value will not be overwritten in the next cycle
var j = i;
// use j instead of i
Thread workThread = new Thread(() => obj[j].DoWork(bufferExecutions));
workThread.IsBackground = true;
workThread.Start();
}
自C#5.0起,您还可以将for
循环转换为foreach
。有一个突破性的变化,&#34;修复&#34;这个问题:
// This will work correctly only with C# 5.0 or newer
foreach(var current_obj in obj)
{
Thread workThread = new Thread(() => current_obj.DoWork(bufferExecutions));
workThread.IsBackground = true;
workThread.Start();
}