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

  • 相关阅读:
    C#中的cookie编程
    C# 键值对数据排序
    C#操作json类型数据
    深刻理解C#的传值调用和传引用调用
    .Net程序员玩转Android开发--ListView单击事件
    C#与Visual Basic的异与同
    初识C#程序结构
    【转】Java学习---线程间的通信
    【转】Mysql学习---MySQL悲观锁中的排它锁
    Linux uptime命令详解
  • 原文地址:https://www.cnblogs.com/weiyuncai/p/4241942.html
Copyright © 2011-2022 走看看