zoukankan      html  css  js  c++  java
  • C# 一个简单的秒表引发的窗体卡死问题

    一个秒表程序也是我的一个心病,因为一直想写这样的一个东西,但是总往GUI那边想,所以就比较怵,可能是上学的时候学MFC搞出的后遗症吧,不过当我今天想好用Win Form(话说还是第一次写win form)写这么一个东西的时候,居然so easy。

    所以说,做不了不可怕,怕的是你不去做,因为你不去做,你就永远不知道你能不能做它。事实证明,大部分你犹豫能不能做的事情,实际上你都能搞定。

    虽然成功实现了一个秒表的简单功能,即开始计时和停止。但是却引发了一个关于win form和C#线程的问题。

    下面一个一个来,先说一下秒表的类实现

    namespace Utils
    {
        public class Time
        {
            private int _minute;
            private int _second;
            private bool _flag;//线程标识
            private Thread _TimingThread = null;
    
            public Time()
            {
                this._minute = 0;
                this._second = 0;
                this._flag = true;
            }
            /// <summary>
            /// 开始计时
            /// </summary>
            public void Start()
            {
                if (_TimingThread == null)
                {
                    _TimingThread = new Thread(new ThreadStart(AddSecond));
                    _TimingThread.Start();
                }
            }
            /// <summary>
            /// 线程执行方法
            /// </summary>
            private void AddSecond()
            {
                while(_flag)
                {
                    Thread.Sleep(1000);
                    if (this._second == 59)
                    {
                        this._minute++;
                        this._second = 0;
                    }
                    else
                    {
                        this._second++;
                    }
                }
            }
            /// <summary>
            /// 格式化显示计时结果
            /// </summary>
            /// <returns></returns>
            public string FormatTimeResult()
            {
                string minute = string.Empty;
                string second = string.Empty;
                if (this._minute < 10)
                {
                    minute = "0" + this._minute.ToString();
                }
                else 
                {
                    minute = this._minute.ToString();
                }
                if (this._second < 10)
                {
                    second = "0" + this._second.ToString();
                }
                else
                {
                    second = this._second.ToString();
                }
                return minute + ":" + second;
            }
            /// <summary>
            /// 停止
            /// </summary>
            public void Stop()
            {
                this._flag = false;
            }
            /// <summary>
            /// 归0操作
            /// </summary>
            public void Zero()
            {
                this._minute = 0;
                this._second = 0;
            }
        }
    }

    秒表的实现还是比较简单的,感觉这样写,也方便以后做扩展。

    下面说说win form方面

    窗体就是这样,一个label,两个button

    QQ图片20140613105209

    最开始,我写了这样一段代码

        public partial class Form1 : Form
        {
            private Time mTime = null;
            private Thread mDisplayThread = null;
            public Form1()
            {
                InitializeComponent();
                mTime = new Time();//实例化秒表类
            }
            private void button_start_Click(object sender, EventArgs e)
            {
                mTime.Start();
                mDisplayThread = new Thread(new ThreadStart(DisplayCurrentTime));
                mDisplayThread.Start();
                button_start.Enabled = false;
            }
    
            public void DisplayCurrentTime()
            {
                while (true)
                {
                    Thread.Sleep(1000);
                    label_Time.Text = mTime.FormatTimeResult();//对Label标签进行实时更新
                    Console.WriteLine("{0}", mTime.FormatTimeResult());
                }
            }
            private void button_stop_Click(object sender, EventArgs e)
            {
                mTime.Stop();
                button_start.Enabled = true;
            }
    }

    这样写感觉思路上没什么问题,当点击【开始计时】按钮的同时创建一个线程,而这个线程是用来每隔一秒去更新一下label上的显示计时时间。

    然而,之后却报一个这样的错误:Cross-thread operation not valid: Control 'label_Time' accessed from a thread other than the thread it was created on.

    网上查了一下,这个错误貌似很常见,MSDN上也给了一个出现此错误的原因,是这样说的,当您试图从单独的线程更新一个win form时,会出现这个错误。

    查了一下,就是说win form上的控件属性想要进行修改的时候,只能在创建Control的线程里调用,不能在以外的线程被调用。而上面的

    label_Time.Text = mTime.FormatTimeResult();

    这段代码呢恰恰是发生在新创建的线程之中,所以就会报错了。

    解决办法是用delegate(委托)加上control.Invoke去联合实现。下面看看实现部分

        public partial class Form1 : Form
        {
            private Time mTime = null;
            private Thread mDisplayThread = null;
            public delegate void UpdateLabel();//声明一个委托
            public UpdateLabel updateLabel;//定义一个委托
    
            public Form1()
            {
                InitializeComponent();
                mTime = new Time();
                updateLabel = new UpdateLabel(UpdateTime);//实例化一个委托对象
            }
    
            private void button_start_Click(object sender, EventArgs e)
            {
                mTime.Start();
                mDisplayThread = new Thread(new ThreadStart(DisplayTimeFunc));
                mDisplayThread.Start();
                button_start.Enabled = false;
    
            }
            /// <summary>
            /// 线程执行方法
            /// </summary>
            public void DisplayTimeFunc()
            {
                while (true)
                {
                    Thread.Sleep(1000);
                    this.Invoke(this.updateLabel);
                }
            }
            /// <summary>
            /// 单独对Label进行刷新
            /// </summary>
            public void UpdateTime()
            {
                label_Time.Text = mTime.FormatTimeResult();
            }
    
            private void button_stop_Click(object sender, EventArgs e)
            {
                mTime.Stop();
                button_start.Enabled = true;
            }
    
        }

    这段代码里mDisplayThread线程执行了DisplayTimeFunc方法,而DisplayTimeFunc方法里实际就是在更新label,不同的是使用了Control.Invoke方法,上面不是说对控件属性的更改要在创建控件的线程里才执行吗?现在看起来好像还是老样子。那是因为我们不了解Control.Invoke是什么东东。MSDN上的解释是:在拥有此控件的基础窗口句柄的线程上执行指定的委托。OK,明白了,this.updateLabel这个委托最后还是在窗口创建的线程中执行的。

    回头想想,其实思路也比较简单,就是先将更改控件属性的操作放在一个方法里,然后写个委托,再写个线程,在线程的执行方法中调用这个委托就OK啦。

    不过到这还不算全完,还有一个小问题,就是当我计时之后,想要关闭这个窗体的时候,发现又开始报错了:

    Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

    研究了一下发现了出现此问题的原因,就是我们“上完厕所没有擦PP”,上面的代码中没有一个操作是对 mDisplayThread 这个线程做了终止的动作。

    所以我们还需要添加以下动作

            private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                mDisplayThread.Abort();
            }

    这样就完整了,在关闭Form1窗体之前,先把线程终止。

    做这个小东西的时候居然连带着让我了解了一些委托和Control.Invoke以及线程的知识点。我会找个时间好好把这部分看看的,争取能总结点什么出来。

    1、Live your own, then enough. 2、Enjoy life, enjoy work. 新浪微博:http://weibo.com/zhouhongyu1989 欢迎围观~! 友情链接:http://zhouhongyu1989.blog.51cto.com/
  • 相关阅读:
    odoo11 审批流中行总额与申请单总额的计算问题
    odoo11 systemd service自动启动配置
    odoo11登录之后返回的session信息分析
    odoo 11 配置nginx反向代理
    odoo 11 之signup_with_phone模块分析
    Ionic1 环境破坏后程序重新恢复过程
    odoo11 安装python ldap
    记上海技术交流会之行备忘录(superset与odoo整合)
    C# 函数式编程:LINQ
    如何在 ASP.NET Core 测试中操纵时间?
  • 原文地址:https://www.cnblogs.com/zhouhongyu1989/p/3785824.html
Copyright © 2011-2022 走看看