计时器小程序——由浅入深实例讲解

  本菜在实现简单的计时器过程中遇到问题的一些成长笔记,有不完整观点的话请多多见谅,也看了众多大神的博客才整理的笔记,下面来实现个人写的小程序。

首先第一个实例(很简单):

  winform窗体包含两个控件:label、button控件,点击控件开始计时,代码如下:

namespace Timer_Test
{
    public partial class CommonInstance : Form
    {
        private static int startTime = 0;

        public CommonInstance()
        {
            InitializeComponent();
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            GetTimes(); //方法在外部封装,调用即可,你应该知道封装的好处咯
        }
        //写个方法,用于计时运算,不能是静态方法,static和this不能共存;
        private void GetTimes()  
        {
            startTime += 1;
            this.lb_getTimes.Text = Convert.ToString(startTime);
        }
    }
}

  哈哈,这个很简单实现吧,那么怎样才能让计时器自动计时呢,相信你已经想到timer控件,没错接下来实现下!

  实现代码如下:

namespace Timer_Test
{
    public partial class CommonInstance : Form
    {
        private static int startTime = 0;

        public CommonInstance()
        {
            InitializeComponent();
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            //GetTimes();
            timer1.Start();
        }
private void timer1_Tick(object sender, EventArgs e)
        {
            startTime += 1;
            lb_getTimes.Text = startTime.ToString();
        }
        private void CommonInstance_Load(object sender, EventArgs e)
        {
            //窗体加载时设置好timer的Enable属性为true:可用
            timer1.Enabled = true;
            timer1.Interval = 1000; //设置间隔时间为1s
        }
    }
}

  其实你会发现,使用timer控件实现也很简单嘛,很多工作都是timer自己做了,省事多了,但我写这编文字的重点不仅仅这些,下面来说下重点吧:

  多线程实现的计时器,不用timer控件了,自己写个机制实现它(这会让我们学到更多的知识),老实说,我还是第一次接触线程,刚开始真的是摸

  不着头脑咋用来着捏,下面就细细说来.....

使用线程前,先引入命名空间:using System.Threading; 具体代码如下:

namespace Timer_Test
{
    public partial class CommonInstance : Form
    {
        private static int startTime = 0;

        Thread thread = null;//声明一个线程

        public CommonInstance()
        {
            InitializeComponent();
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            //GetTimes();
            //timer1.Start();

            thread = new Thread(new ThreadStart(GetTimes)); //创建开始线程实例,将要执行的方法作为参数
            thread.Start();
        }
         private void GetTimes()  
        {
            startTime += 1;
            this.lb_getTimes.Text = Convert.ToString(startTime);
        }
}

  

  这时你就会暗暗自喜,线程也不过如此嘛,当你点击开始计时后就报错了哦,报错是你给lable控件赋值之时,为什么会错呢,因为你跨线程给UI界面控件赋值了,这关系

到数据的安全问题。

 

  c#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。此时它将会在内部调用new MethodInvoker(LoadGlobalImage)来完成下面的步骤,这个做法保证了控件的安全,你可以这样理解,有人想找你借钱,他可以直接在你的钱包中拿,这样太不安全,因此必须让别人先要告诉你,你再从自己的钱包把钱拿出来借给别人,这样就安全了。

这样就可以很好的理解上面那个winform程序中 this.BeginInvoke(fc);这行code了,这个执行后其实也就是主线程在调用fc中绑定的方法ThreadFuntion()了,这种方式其实相当于把这个新开的线程“注入”到了主控制线程中,它取得了主线程的控制。只要这个线程不返回,那么主线程将永远都无法响应。就算新开的线程中不使用无限循环,使可以返回了。这种方式的使用多线程也失去了它本来的意义。(copy过来的,源地址在最后面)

  

  解决方法一:在点击事件里加这句代码:Control.CheckForIllegalCrossThreadCalls = false; // 默认为true;

  意思是:不检查跨线程,这是不安全的,不推荐使用;

  解决方法二:(这篇文章的意义所在了)

  通常的方法是使用委托delegate委托主线程处理(解释是后面的啰嗦),和BeginInvoke()方法、IsBackground属性值(默认为false:即非后台线程)以及属

性InvokeRequired;

  下面就要用到InvokeRequired这个propety

  

解决问题代码如下:

namespace Timer_Test
{
    public partial class CommonInstance : Form
    {
        private delegate void FlushClient(); //定义一个委托代理,前面要和委托代理函数签名一致

        private static int startTime = 0;

        Thread thread = null;//声明一个线程

        public CommonInstance()
        {
            InitializeComponent();
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            //GetTimes();
            //timer1.Start();

            thread = new Thread(new ThreadStart(GetTimes));
            thread.IsBackground = true;  //设置为后台线程
            thread.Start();
        }

        //写个方法,用于计时运算,不能是静态方法,static和this不能共存;
        private void GetTimes()  
        {
            while (true)  //无限循环
            {
                Thread.Sleep(1000);  //睡眠时间为一秒
                ThreadFunction();
            }
 
        }
        //写个委托代理函数
        private void ThreadFunction()
        {
                if (this.InvokeRequired) //等待异步
                {
                    FlushClient fc = new FlushClient(ThreadFunction);//实例化委托
                    this.BeginInvoke(fc); //通过代理刷新UI
                }
                else
                {
                    startTime += 1;
                    this.lb_getTimes.Text = Convert.ToString(startTime);
                }
        }

  这里我就啰嗦一下咯........

  怎么说呢:这时你应该弄清楚 线程 是如何工作的?

  在C#中,非主线程(即非UI线程,就是通过new Thread创建的线程)是不能直接操作UI元素的,必须通过Handler与UI线程通讯,通知UI线程更新.而C#则采用委托的方式更

新UI元素。

  线程分为前台线程和后台线程,线程默认为前台线程(主线程),这意味着任何前台线程在运行都会保持程序存活。

后台线程:只要有一个前台线程在运行,应用程序的进程就在运行。如果多个前台线程在运行,而Main()方法结束了,应用程序的进程就是激活的,直到所有前台线程完成其任务为止。

前台线程和后台线程的唯一的区别是— 后台线程不会阻止进程终止。

在默认情况下,用Thread 类创建的线程都是前台线程。线程池中的线程总是后台线程。

参考:http://www.cnblogs.com/Linford-Xu/archive/2012/09/19/2693340.html