您的位置:首页 > 职场人生

【黑马程序员】多线程的方法重入问题

2013-09-23 16:43 155 查看
---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流!
----------------------

由于多线程是让CPU在“同一时间”响应用户不同的操作,因此在程序运行过程中,很容易产生一些让人意想不到的结果。在说方法重入问题之前,先看一个示例吧。

有一个如图所示的窗体应用程序,窗体里面有一个TextBox控件和一个Button控件,TextBox控件的初始值为0:


下面是主要方法的代码 :

/// <summary>
/// 多线程方法重入问题按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnMethodProblem_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ChangeText);    //这里可以直接把方法传入
thread.IsBackground = true;    //把线程设置为后台线程
thread.Start();
}

/// <summary>
/// 修改文本框里面的内容(循环取得文本框的值,然后让它自增,最后再把它赋值给文本框)
/// </summary>
private void ChangeText()
{
for (int i = 0; i < 10000; i++)
{
int a = int.Parse(txtNum.Text);
a++;
txtNum.Text = a.ToString();
}
}

运行这段程序,其结果如图所示:


程序运如图所示的位置就会报错,这是什么原因呢?从报错的信息中,我们可以大概了解到,txtNum这个控件是由主线程创建的,但是我们是在另外一个新建的线程中调用它,这是不允许的,所以线程之间的操作无效。在主线程里面创建的控件只能在主线程里面使用,不能使用其它线程。但这不是绝对的,我们可以在初始化窗体的时候使用如下图所示的一段代码把微软的检查关掉。



我们再运行程序,点击按钮,这时就不会报错了。运行结果如下图所示,由于循环了10000次,因此txtNum的值最后是10000。



下面,我们再把这段程序做一个改动,在代码中增加一个线程,这两个线程都调用同一个方法,代码如下所示:

/// <summary>
/// 多线程方法重入问题按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnMethodProblem_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ChangeText);    //这里可以直接把方法传入
thread.IsBackground = true;    //把线程设置为后台线程
thread.Start();

//增加一个线程
Thread thread2 = new Thread(ChangeText);    //这里可以直接把方法传入
thread2.IsBackground = true;    //把线程设置为后台线程
thread2.Start();
}

/// <summary>
/// 修改文本框里面的内容(循环取得文本框的值,然后让它自增,最后再把它赋值给文本框)
/// </summary>
private void ChangeText()
{
for (int i = 0; i < 10000; i++)
{
int a = int.Parse(txtNum.Text);
a++;
txtNum.Text = a.ToString();
}
}

再次运行程序,点击按钮,我们会得到什么结果呢?按照我们一贯的思维理解,既然ChangeText()方法被调用了两遍,那么两个线程都执行完毕之后,txtNum这个控件的值应该是20000。真的是这个值吗?下图是这段程序的运行结果:



程序的实际结果大大出乎我们的预料,才10004,没到20000。这是怎么回事呢?这实际就是线程重入问题的表象。我们可以在ChangeText()方法的for循环里面加上一段代码来调试查看a值的变化情况。并且我们还可以给每个线程取一个名字,然后在输出窗口里面查看CPU当前调用了哪个线程。代码如下:

/// <summary>
/// 多线程方法重入问题按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnMethodProblem_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ChangeText);    //这里可以直接把方法传入
thread.Name = "T1";      //给线程取名为“T1”
thread.IsBackground = true;    //把线程设置为后台线程
thread.Start();

//增加一个线程
Thread thread2 = new Thread(ChangeText);    //这里可以直接把方法传入
thread2.Name = "T2";    //给线程取名为“T2”
thread2.IsBackground = true;    //把线程设置为后台线程
thread2.Start();
}

/// <summary>
/// 修改文本框里面的内容(循环取得文本框的值,然后让它自增,最后再把它赋值给文本框)
/// </summary>
private void ChangeText()
{
for (int i = 0; i < 1000; i++) //把循环次数改小一点,不然程序运行时间太长
{
int a = int.Parse(txtNum.Text);
//在这里利用Thread类的CurrentThread属性获取当前正在运行的线程,然后在输出窗口中
//打印出它的名字,并且打印出当前获取到的a的值和程序的运行次数,也就是i的值
Console.WriteLine(Thread.CurrentThread.Name + ",a=" + a + ",i=" + i.ToString());
a++;
txtNum.Text = a.ToString();
}
}

再次运行程序,在点击按钮之前先打开输出窗口,以便在程序运行时观察值的变化。结果如图所示:


从图中,我们可以看到,当线程T2取出txtNum里面的值之后,并没有马上进行运算,而是把CPU的使用权交给线程T1,让T1再次去取txtNum控件的值,由于此时txtNum的值没有改变,因此T1取出的值与T2一样。也就是说,txtNum的值大部分都要被取出两遍,但不是所有的值都会被取两遍。这是因为所有线程的开始和结束时间都有先后之分,当T2线程结束之后,就只有T1线程去取txtNum的值。由最后i的值可以看出,每个线程都执行了1000遍循环。当两个线程同时去获取一个相同控件的值的时候,会轮流获取。

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流!
----------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐