最近几周一直在做DotNET WinForm开发,陆陆续续有些收获,希望能够有空好好整理整理。记下来以免以后又忘了。:-)
一、最简单的线程使用方法
新建一个C# Windows应用程序项目,在最前面的引用代码那增加一行
using System.Threading;
在界面上扔个Button和Label,再写几行简单的代码,就是一个最简单的线程例子啦
private void button1_Click(object sender, System.EventArgs e)
{
Thread t = new Thread(new ThreadStart(myRun));
t.Start();
}
private void myRun()
{
for(int i=0; i<1000000; i++)
{
if (i % 1000 == 0)
label1.Text = i.ToString();
}
}
当然,这个例子没有处理线程之间同步之类关系。你试试快速点几下Button就知道有什么好玩的事情发生了。
二、给线程传递参数
ThreadStart 委托没有参数也没有返回值。其声明为 public delegate void ThreadStart();
所以不能直接给线程传递参数。但是我们可以把线程函数封装到一个类里,给类的实例传递参数(可以在创建实例时,也可以用另外的函数来传递。这不是重点)。因为DotNET自由线程的特点,在线程中是可以访问同一个类里的数据的。
我们更改上面的简单例子,尝试给线程传递一个循环的终止值。
首先是弄个类把 myRun 函数装进去 :-) 注意要公开函数(public)
public class myThreadClass
{
private int Max = 0;
public myThreadClass(int initValue)
{
Max = initValue;
}
public void myRun()
{
for(int i=0; i {
if (i % 1000 == 0)
label1.Text = i.ToString();
}
}
}
然后,Button事件有点小改动,如下:
myThreadClass myThread = new myThreadClass(800000);
Thread t = new Thread(new ThreadStart(myThread.myRun));
t.Start();
仅仅是多了一行,很简单是吧?
编译,出错啦!找不到类型或命名空间名称"label1"
注意到我们原来是直接在Form实例中使用label1,现在将myRun装到另外的类里,当然不能直接访问label1啦。怎么办?
一样,弄成个参数,传给myThreadClass就行。修改后的程序如下:
public class myThreadClass
{
private int Max = 0;
object obj = null;
public myThreadClass(int initValue, object initObj)
{
Max = initValue;
obj = initObj;
}
public void myRun()
{
for(int i=0; i {
if (i % 1000 == 0)
if (obj != null)
(obj as Label).Text = i.ToString();
}
}
}
下面是Form1中的按钮事件
private void button1_Click(object sender, System.EventArgs e)
{
myThreadClass myThread = new myThreadClass(800000, label1);
Thread t = new Thread(new ThreadStart(myThread.myRun));
t.Start();
}
好了,运行下看看,是不是和原来的效果一模一样。差别在于调用线程的时候,你可以传递参数,把握线程的运行时间。
三、获得线程的返回值
第二部分解决了线程参数的问题,这部分我们来解决返回值的问题。
我们注意到,第二部分的代码,会把线程的中间运行状态的值写到Form1的label1.Text中,那么,我们能不能从这里动手脚呢?试试看。
往Form1上扔个进度条ProgressBar先,myThreadClass我们暂时先不动,只改Button事件:
private void button1_Click(object sender, System.EventArgs e)
{
const int Max = 800001;
progressBar1.Maximum = Max;
progressBar1.Value = 0;
myThreadClass myThread = new myThreadClass(Max, label1);
Thread t = new Thread(new ThreadStart(myThread.myRun));
t.Start();
while ( t.IsAlive )
{
progressBar1.Value = Int32.Parse(label1.Text.ToString());
progressBar1.Refresh();
Thread.Sleep(0);
}
}
运行一下,结果是不行!窗体完全失控了。如图:
分析下原因。很显然,是那个while搞的鬼!让窗体主线程在这里不停的循环、执行。根本没有多余的力气来更新窗体界面显示啦!
此路不通!那怎么办好呢?答案就是 回调函数。
首先,声明一个回调函数原型,在我们这个例子中,只需要取得一个返回值,所以回调函数的参数只有一个,如果有更多返回值,可相应修改。
namespace TestThread
{
public delegate void ThreadCallback(int i);
public class Form1 : System.Windows.Forms.Form
然后修改myThreadClass类,不再需要传递label1给线程了。因为我们将在回调函数中获得线程当前循环的值,然后由回调函数自个来更新label1.Text,同时还要更新progressBar1。
但要传递给线程的参数扔是两个,一个是initValue用来控制循环的,一个是ThreadCallback callbackDelegate,即回调函数。修改后的myThreadClass类代码如下:
public class myThreadClass
{
private int Max = 0;
private ThreadCallback callback;
public myThreadClass(int initValue, ThreadCallback callbackDelegate)
{
Max = initValue;
callback = callbackDelegate;
}
public void myRun()
{
for(int i=0; i {
if (i % 1000 == 0)
if (callback != null)
callback(i);
}
}
}
回到Form1,先写个回调函数ThreadCallback 的具体实现
public void myCallback(int i)
{
label1.Text = i.ToString();
label1.Refresh();
progressBar1.Value = i;
progressBar1.Refresh();
}
接着修改Button事件
private void button1_Click(object sender, System.EventArgs e)
{
const int Max = 800001;
progressBar1.Maximum = Max;
progressBar1.Value = 0;
myThreadClass myThread = new myThreadClass(Max, new ThreadCallback(myCallback));
Thread t = new Thread(new ThreadStart(myThread.myRun));
t.Start();
}
代码中,通过new ThreadCallback(myCallback),给线程传了回调函数。
OK!
改动都不算多。我们运行下看看吧!一切顺利!如图:
label1和progressBar1同步更新状态。而且在线程运行时,拖动主窗体也不会失去控制了,没有任何问题。
后文
本文是《DotNET多线程使用初探》,故不是详细的DotNET多线程使用说明。多线程还有很多其它方面,如生存期、线程间同步、死锁问题、STA、MTA、线程池等等等等。
本文起源于我在DotNET开发中,处理一些复杂的数据库操作非常耗时,主窗体经常失去反应。这时就需要一些简单的线程操作。很必要的一个是给用户一个进度条。
如果你遇到的情况跟我相似,相信本文对你会有所帮助。