开头先看一篇文章:【转】编写高质量代码改善C#程序的157个建议——建议75:警惕线程不会立即启动 - 指间的徘徊 - 博客园d
摘抄:
static int _id = 0;
static void Main()
{
for (int i = 0; i < 10; i++, _id++)
{
Thread t = new Thread(() =>
{
Console.WriteLine(string.Format("{0}:{1}",
Thread.CurrentThread.Name, _id));
});
t.Name = string.Format("Thread{0}", i);
t.IsBackground = true;
t.Start();
}
Console.ReadLine();
}
以上代码的可能输出为:
Thread0:2
Thread4:5
Thread2:3
Thread1:3
Thread5:5
Thread6:6
Thread7:7
Thread8:9
Thread3:3
Thread9:10
这段代码的输出从两个方面印证了线程不是立即启动的。
首先,我们看到线程并没有按照顺序启动。在代码逻辑中,前面Start的那个线程也许迟于后Start的那个线程执行。
其次,传入线程内部的ID值,不再是for循环执行中当前的ID值。以Thread9为例,在for循环中,其当前的值为9,而Thread9真正得到执行的时候,ID却已经跳出循环,早已经变为10了。
要让需求得到正确的编码,需要把上面的for循环修改成为一段同步代码:
static int _id = 0;
static void Main()
{
for (int i = 0; i < 10; i++, _id++)
{
NewMethod1(i, _id);
}
Console.ReadLine();
}
private static void NewMethod1(int i, int realTimeID)
{
Thread t = new Thread(() =>
{
Console.WriteLine(string.Format("{0}:{1}",
Thread.CurrentThread.Name, realTimeID));
});
t.Name = string.Format("Thread{0}", i);
t.IsBackground = true;
t.Start();
}
}
以上代码输出:
Thread0:0
Thread3:3
Thread1:1
Thread2:2
Thread5:5
Thread4:4
Thread6:6
Thread7:7
Thread8:8
Thread9:9
可以看到,线程虽然保持了不会立即启动的特点,但是传入线程的ID值,由于在for循环内部变成了同步代码,所以能够正确传入。
疑问:
上面这篇文章说的在for循环内部变成了同步代码是是什么意思,原来不也是for循环吗?
解答:
第一段
所有线程捕获的是同一个 _id
变量(闭包特性)。当线程真正执行时,_id
可能已经被循环多次递增(例如,Thread0
启动时 _id
可能已经是 10)。
输出中 _id
的值是混乱的(如 Thread9:10
),因为线程执行时 _id
已经变成循环结束后的值。
第二段
通过方法参数 realTimeID
传递 _id
的当前值(值传递而非引用捕获)。
每个线程的 realTimeID
是调用 NewMethod1
时传入的 _id
的瞬时值(值类型复制),因此即使 _id
后续被修改,线程内部的值也不会变。
虽然线程启动顺序仍不确定,但每个线程的 realTimeID
是正确的(如 Thread0:0
, Thread1:1
)