zoukankan      html  css  js  c++  java
  • 计时器小程序——由浅入深实例讲解

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

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

      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

  • 相关阅读:
    CSS盒子模型
    getContextPath、getServletPath、getRequestURI、request.getRealPath的区别
    MYSQL中的CASE WHEN END AS
    单点登录的精华总结
    git&github
    June 21st 2017 Week 25th Wednesday
    June 20th 2017 Week 25th Tuesday
    June 19th 2017 Week 25th Monday
    June 18th 2017 Week 25th Sunday
    June 17th 2017 Week 24th Saturday
  • 原文地址:https://www.cnblogs.com/weiyuncai/p/4241942.html
Copyright © 2011-2022 走看看