刚开始编程的时候,对多线程有着盲目的崇拜。遇到需要调用写的方法,就想用多线程来进行调用。结果这几天才发现了软件中的BUG,看来多线程也不是想用就能用的,用不好就会非常糟糕,导致一些莫名其名的BUG。
我写了一个数据库的小例子,也验证了这个BUG是确实存在的。首先呢,我在数据库中创建了两个字段的表格,两个字段分别为M,N。其中M我设置为主键,并手动添加了从1到10的数据,再通过数据库更新的方式来对这10个数据进行更新。
int[] array = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] array1 = new int[10] {21,22,23,24,25,26,27,28,29,30 };
private void update(int i)
{
string sqlstr = null;
sqlstr += "update Table_1 set N='"+array1[i]+ "' where M='"+ array[i] + "'";
if (db.ExecDataBySql(sqlstr)>0)
{
MessageBox.Show("zhixingchengong!");
}
}
然后添加了一个Button控件来执行调用这个方法,如下:
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
if (i > 9) i = 9;
new Task(()=> update(i)).Start() ;
}
}
之所以添加if (i > 9) i = 9;是因为程序莫名其妙的发生超出索引的异常,很是奇怪。虽然加了这个,程序依然会出现报错,无解。执行这个程序之后,发现数据只成功更新了3个数据。
最后才看到一段类似于我这样的多线程问题。
Lambda 表达式与被捕获变量
如我们所见,lambda 表达式是向线程传递数据的最强大的方法。然而必须小心,不要在启动线程之后误修改被捕获变量(captured variables)。例如,考虑下面的例子:
for (int i = 0; i < 10; i++)
new Thread (() => Console.Write (i)).Start();
输出结果是不确定的!可能是这样0223557799。
问题在于变量i在整个循环中指向相同的内存地址。所以,每一个线程在调用Console.Write时,都在使用这个值在运行时会被改变的变量!
类似的问题在C# 4.0 in a Nutshell的第 8 章的 “Captured Variables” 有描述。这个问题与多线程没什么关系,而是和 C# 的捕获变量的规则有关(在for和foreach的场景下有时不是很理想)。
解决方法就是使用临时变量,如下所示:
for (int i = 0; i < 10; i++)
{
int temp = i;
new Thread (() => Console.Write (temp)).Start();
}
变量temp对于每一个循环迭代是局部的。所以,每一个线程会捕获一个不同的内存地址,从而不会产生问题。我们可以使用更为简单的代码来演示前面的问题:
string text = "t1";
Thread t1 = new Thread ( () => Console.WriteLine (text) );
text = "t2";
Thread t2 = new Thread ( () => Console.WriteLine (text) );
t1.Start();
t2.Start();
因为两个lambda表达式捕获了相同的text变量,t2会被打印两次:
t2
t2
看来线程也不是随便用的,我还是要慢慢搞懂每一个问题,让自己变得越来越强,程序越来越好看。