上篇博客从线程的基本概况开始着重讨论了线程,进程,程序之间的区别,然后讨论了线程操作的几个类,并通过实例来说明了线程的创建方法。本篇博客将会带大家更深入的了解线程,介绍线程的基本方法,并通过一个Demo使用委托来调用线程之外的对象。
前篇博客基础:【GDI+编程--番外篇(二)】--从事件看委托
【.NET线程--开篇】--线程从零开始
多线程的使用会帮助程序提高响应速度,因为可以同时执行多个任务这样对比一个个的来完成任务来说提高了响应的速度,较之添加多CPU来说多线程提高了强大的技术来执行多个任务。虽然多线程提高了响应速度,但同时牺牲了资源,由于多线程的执行它会占用多个资源,为了避免资源访问的冲突,往往会在每个线程中都会创建自己的资源,这样导致了资源的浪费。另外如果线程过多,则其中大多数线程都不会产生明显的进度,如果大多数当前线程处于一个进程中,则其他进程中的线程的调度频率就会很低。
下表包括了在线程编程过程中常用的基本方法。
可用于控制单个线程的方法
方法 | 操作 |
---|---|
Start | 使线程开始运行。 |
Sleep | 使线程暂停指定的一段时间。 |
Suspend | 在线程到达安全点时,使其暂停。 |
Abort | 在线程到达安全点时,使其停止。 |
Resume | 重新启动挂起的线程 |
Join | 使当前线程一直等到另一线程完成。 在与超时值一起使用时,如果该线程在分配的时间内完成,此方法将返回 True。 |
Note: 安全点是指代码中公共语言运行时可以安全地执行自动“垃圾回收”的位置。垃圾回收是指释放不再使用的变量并回收内存的过程。 调用线程的 Abort 或 Suspend 方法时,公共语言运行时将对代码进行分析,确定让线程停止运行的适当位置。
自己做的一个小Demo来实现多线程,当点击开始按钮后会在文本框中填写数字,与此同时加载进度条,读取进度,点击暂停后线程会停止。另外可以在文本框中输入暂停时间来指定线程暂停时间,在暂停后继续执行。
Demo下载地址:线程常用方法示例
在点击开始按钮后会同时创建两个线程,分别为showNumThread和pBarThread,用来向文本框中写入数字和加载进度条。这里需要说明的是,一般情况下线程内部是不允许调用线程外创建的对象的,创建的两个线程都调用了线程外部的对象,是怎么实现的呢?使用的是委托来异步执行程序来实现了调用线程外部的方法。
/// <summary> /// 开始按钮事件,创建线程并为线程指定方法 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, EventArgs e) { pBarThread = new Thread(new ThreadStart(this.ExepBarShow)); //创建进度条线程 showNumThread = new Thread(new ThreadStart(this.ExeShowNum)); //创建显示文本框中的文字线程 //开始两个已创建的线程 this.StartThread(showNumThread); this.StartThread(pBarThread); } /// <summary> /// 使用委托执行ShowNumToText方法 /// </summary> private void ExeShowNum() { try { MethodInvoker mInvoker = new MethodInvoker(this.ShowNumToText); //声明托管委托,并为委托执行执行的方法 //执行委托方法,向Text中写入文字 while (true) { this.BeginInvoke((Delegate)mInvoker); //异步执行执行的委托 Thread.Sleep(1000); //线程停顿1秒后继续执行 } } catch { } } /// <summary> /// 先文本框txtNum中写入文字 /// </summary> private void ShowNumToText() { i = i + 1; //i累加 txtNum.Text = txtNum.Text + " " + (i + 1).ToString(); //向txtNum中写入文字 } /// <summary> /// 执行pBarShow方法,加载进度条,让进度条读取进度 /// </summary> private void ExepBarShow() { try { MethodInvoker mInvoker = new MethodInvoker(this.pBarShow); //声明并创建委托,为委托执行进度 //异步执行委托 while (true) { this.BeginInvoke((Delegate)mInvoker); Thread.Sleep(10); } } catch { } } /// <summary> /// 执行进度条读取进度 /// </summary> private void pBarShow() { this.pgBar.PerformStep(); } /// <summary> /// 线程开始方法 /// </summary> /// <param name="th">Thread对象,需要开始的线程</param> private void StartThread(Thread th) { th.Start(); } /// <summary> /// 线程结束方法 /// </summary> /// <param name="th">Thread对象,需要结束的线程</param> private void EndThread(Thread th) { th.Interrupt(); //中断线程 th.Abort(); //终止线程 th = null; } /// <summary> /// 停止线程事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStop_Click(object sender, EventArgs e) { try { this.TestThead(); //验证线程是否存在,如果没有存在将会抛错 this.EndThread(this.pBarThread); //结束线程 this.EndThread(this.showNumThread); //结束线程 } catch (Exception ex) { //提示错误信息 MessageBox.Show(ex.Message , "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } /// <summary> /// 终止线程事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnEnd_Click(object sender, EventArgs e) { try { this.TestThead(); //验证线程是否创建 this.EndThread(this.pBarThread);//结束线程 this.EndThread(this.showNumThread); //结束线程 txtNum.Text = ""; //清空文本框内容 i = 0; //数字充值 this.pgBar.Value = 0;//进度条重置 } catch (Exception ex) { //显示错误信息 MessageBox.Show(ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } /// <summary> /// 执行指定线程停顿时间 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStopMinute_Click(object sender, EventArgs e) { try { int j = int.Parse(textBox1.Text); //获取终止的时间 Thread.Sleep(j); //将线程暂停指定的时间 } catch (Exception ex) { MessageBox.Show(ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } /// <summary> /// 验证线程是否存在方法 /// </summary> private void TestThead() { if (pBarThread ==null) { throw new Exception ("未创建线程,请创建线程后操作!"); } if (showNumThread == null) { throw new Exception ("未创建线程,请创建线程后操作!"); } }
Join方法能在指定的线程中插入一个线程,当插入的线程执行完成后才会继续执行被插入的线程。.NET为我们重载了此方法,能够为方法传递参数来指定经过的时间,此时该方法的作用与Sleep相类似,执行经过多长时间后来执行被插入的线程。Join方法的灵活运行能够实现线程之间的执行顺序。
using System; using System.Threading; namespace TestJoin { /// <summary> /// Join方法验证实例,线程t1使用了join方法,线程t2没有使用join方法 /// </summary> class Program { static void Main(string[] args) { //创建新线程,为线程执行行为 Thread t1 = new Thread(() => { Thread.Sleep(1000); Console.WriteLine("t1 is ending."); }); t1.Start(); //开始线程 t1.Join(); //在主线程中插入t1线程,先执行t1,线程后执行主线程 Console.WriteLine("t1.Join() returned."); //执行主线程,提示t1已经完成 //创建新线程,为线程执行行为 Thread t2 = new Thread(() => { Thread.Sleep(1000); Console.WriteLine("t2 is ending."); }); t2.Start(); //开始线程 Console.WriteLine("t2.Join() returned."); //执行主线程,提示t1已经完成 Console.ReadLine(); } } } /*输出结果: *t1 is ending. *t1.Join() returned. * *t2.Join() returned. *t2 is ending. */输出结果:
从输出结果上分析可以得出,Join方法将创建的线程插入到了主线程中当执行完后再继续执行主线程,对应到Demo2中是线程t1插入到了主线程中,这样会首先执行t1线程在控制台上打印“t1 is ending”打印完成后t1线程结束,然后继续执行主线程来打印其它的文字。所以我们完全可以说Join方法是将一个线程插入到主线程中,当执行完插入的线程后再继续执行被插入的线程。
线程的优缺点决定了在开发过程中是否使用多线程,另外灵活运行单线程的方法来实现灵活的控制线程,两个Demo使用了线程的基本方法,能够更加深刻的了解它们的使用。下篇博客将会更加深入的讨论线程和线程之间的调用关系,以及如何实现线程间的数据传递及检索。