【黑马程序员】多线程

来源:互联网 发布:ubuntu luajit 安装 编辑:程序博客网 时间:2024/06/04 20:10
---------------------- Windows Phone 7手机开发、.Net培训、期待与您交流! ----------------------

线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针,程序计数器等),但代码区是共享的,不同的线程可以执行同样的函数。多线程是指程序中包含多个执行流,即在同一个程序中可以同时运行多个不同的线程来执行不同的任务(代码),也就是说允许单个程序创建多个并发执行的线程来完成各自的任务。

多线程并非真正的同时执行,而是以极快的速度轮流切换执行的,当一个线程执行了一小段时间,要切换到另一个线程时,会将当前的执行进度保存在寄存器里,当再次切换至它时,接着刚才的进度继续执行。

线程运行所执行的代码就是一个方法。所以要new一个线程,先得写好一个方法,然后通过委托将该方法传递给线程的构造函数,然后启动线程执行该方法。

在以前的博客中提到过的System.Media.SoundPlayer的Play(),PlayLooping()方法以及System.Timers.Timer对象执行定期事件都是系统自动分配一个单独的线程来执行的。在程序中自己创建一个新线程是创建一个System.Threading.Thread实例。其构造函数有4种重载:

                public Thread(System.Threading.ParameterizedThreadStart start);                public Thread(System.Threading.ThreadStart start);                public Thread(System.Threading.ParameterizedThreadStart start, int maxStackSize);                public Thread(System.Threading.ThreadStart start, int maxStackSize);

maxStackSize是线程要使用的最大堆栈大小。目前主要用到前面两种构造函数。ParameterizedThreadStart和ThreadStart都是定义在System.Threading命名空间的委托类型:

public delegate void ParameterizedThreadStart(object obj);

public delegate void ThreadStart();

关于委托,学得不是很多,我的初步理解就是定义了一种指向特定的参数列表和返回值类型的方法的数据类型,声明并初始化一个委托变量时,使之指向一个参数和返回值类型与之相匹配的方法。返回值类型必须相同,委托的参数类型要和方法的参数类型相同或前者可以隐式转换为后者。这就解释了为什么在winform中某些事件的委托所要求的第二个参数是EventArgs的派生类型,但是同样可以调用第二个参数为EventArgs的方法。如下面代码所示,Tst委托要求的参数是string,而同样可以指向参数为object类型的方法。

namespace Multithread{    static class Program    {        /// <summary>        /// 应用程序的主入口点。        /// </summary>        [STAThread]        static void Main()        {            Tst tst = new Tst(method1);            tst("如果委托定义时的参数类型可隐式转换为某个方法的参数类型或与之相同,则可创建指向该方法的委托,调用该委托时参数必须是委托定义时的参数类型;" +                "\n如果方法的参数和委托的参数类型不同且后者不能隐式转换为前者,则不能创建指向该方法的委托。返回值类型必须相同。");                    }        static void method1(object obj)        {            MessageBox.Show(obj.ToString());        }    }    delegate void Tst(string s);}

在这里,委托类型ParameterizedThreadStart要求的参数是object类型,object是所有其他类型的基类,不能隐式转换为任何其他类型,因此一个ParameterizedThreadStart变量只能指向参数类型为object,返回值为void的方法。所以要定义一个带参数的方法让新线程执行,该方法只能有一个参数且为object类型,返回值为void,然后在方法中,将这个object参数转换为所需类型,当然通过线程的Start方法传参的时候也得传所需类型的参数。如果需要传多个参数,则把这些参数放在一个数组或者泛型集合等里面作为一个参数。

参数为ThreadStart类型的Thread构造函数,接受无参且返回值为void的方法。

关于创建线程,线程的后台设置,命名,启动,获取当前正在执行的线程名,线程的重入问题,控件的跨线程调用,见以下代码:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Threading;namespace Multithread{    public partial class Form1 : Form    {        public Form1()        {            InitializeComponent();            //允许控件跨线程调用。            Control.CheckForIllegalCrossThreadCalls = false;        }        //单线程计数,点了之后,窗体一时无响应,因为循环计数去了。        private void btnSingleThread_Click(object sender, EventArgs e)        {            Count();        }        private void Count()        {            MessageBox.Show("计时开始");            DateTime beginTime = DateTime.Now;            for (int i = 0; i < int.MaxValue; i++) ;            TimeSpan howLong = DateTime.Now.Subtract(beginTime);            MessageBox.Show("计数结束,用时"+howLong.TotalMilliseconds+"ms");        }        //用一个单独的线程计数        private void btnMultiThread_Click(object sender, EventArgs e)        {            //利用参数类型为ThreadStart的构造函数创建一个线程,即要所执行的方法无参。            System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Count));            thread.Name = "Count";            //设为后台线程,这样主线程UI线程关掉了,后台线程也结束。            //如果是前台线程的话,即使其他前台线程都结束了,也得等它结束了整个程序才结束。            thread.IsBackground = true;            //start()方法,启动线程开始执行委托传入的方法。            thread.Start();            //点了“多线程计数”按钮后UI线程所执行的代码结束,继续监视用户操作。        }        //多线程重入        private void btnReentrancy_Click(object sender, EventArgs e)        {            //新建一个线程,设为后台,命名为T1,去执行Update_txtNum方法。            Thread thread = new Thread(Update_txtNum);            thread.IsBackground = true;            thread.Name = "T1";            //对于ParameterizedThreadStart委托传过来的需要object参数的方法,启动线程时,通过Start()方法传参。            //如果不给参数,则所执行的方法中,object参数将为null.            thread.Start(0);             //thread指向另一个线程对象T2,T2执行与T1同样的方法。            thread = new Thread(Update_txtNum);            thread.IsBackground = true;            thread.Name = "T2";            thread.Start(0);        }        //对文本框中的数字加1,循环1000次。        private void Update_txtNum(object obj)        {            MessageBox.Show(Thread.CurrentThread.Name + " Started.");            txtNum.Text = obj.ToString();            int updatedTimes = 0;            for (; updatedTimes < 1000; )            {                int num = int.Parse(txtNum.Text);                //Thread.CurrentThread.Name,获取当前线程名                Console.WriteLine(Thread.CurrentThread.Name + ": txtNum=" + num);                num++;                txtNum.Text = Convert.ToString(num);                Console.WriteLine("Updated {0} times by {1}",++updatedTimes, Thread.CurrentThread.Name);            }            MessageBox.Show("Thread "+ Thread.CurrentThread.Name + " Completed.");        }    }}

由上图可见,T1最终循环了1000次之后退出(先点的T2的MessageBox的确定,故T2先开始循环,在前面就已循环完1000次),都是从0开始加,可是最终文本框结果为984.这是因为,两个线程执行同一个方法,操纵同一个TextBox控件,轮流执行,就会互相影响执行结果。线程之间的切换不一定刚好发生在执行完一次完整的循环后,有可能在循环中停下来,让另一个线程执行,所以,一个线程读出了文本框中的数字,还没来得及用加1后的结果更新文本框,另一个线程就执行了,读到的还是之前的数字,不知道有没有来得及对文本框中的数字加1 ,之前那个线程又接着执行,将之前读到的数字加1后的结果放入文本框,这样两个线程循环两次却对文本框中的数字只加了1。另外,由于执行该方法的时候传入了参数0,将其写入文本框,这样,当T2已经加到某个数时,T1开始执行了,将文本框中的数字重置为0:

有时候两个线程执行的时间可能并不均衡:

中间有一段时间,T1执行的比较长,将数字连续加了好几次,加到184了,T2才接着上次中断时的进度执行,又把文本框中的数字降为182.

之所以T1,T2在循环的过程中,文本框中的内容会随txtNum.Text的值同步实时变化,是因为控件的刷新是UI线程来执行的。如果是UI线程来执行该方法的话,循环过程中窗体不会响应用户操作,而且直到循环结束,才将txtNum.Text的值一次性显示出来。关于这个及其应用,在之前的WinForm一文中就已涉及到。

 关于多线程,就写这么多吧。还有一点内容就是线程实现带多个参数的方法,很简单,就是把多个参数放在一个集合或者数组中,打包传给要执行的方法,因为ParameterizedThreadStart委托只接受一个object类型的参数,然后在方法中将这个object参数转型为相应的集合或数组,再索引器取值,类型转换等,从而实现多参数的传递。

 ---------------------- Windows Phone 7手机开发、.Net培训、期待与您交流! ---------------------- 详细请查看:http://net.itheima.com/ 

原创粉丝点击