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

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

    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以及线程的知识点。我会找个时间好好把这部分看看的,争取能总结点什么出来。

C# 一个简单的秒表引发的窗体卡死问题

时间: 2024-10-02 23:25:22

C# 一个简单的秒表引发的窗体卡死问题的相关文章

一个简单算法题引发的思考&lt;DNA sorting&gt;(about cin/template/new etc)

首先是昨天在北京大学oj网上看到一个简单的算法题目,虽然简单,但是如何完成一段高效.简洁.让人容易看懂的代码对于我这个基础不好,刚刚进入计算机行业的小白来说还是有意义的.而且在写代码的过程中,会发现自己平时学习中不会发现的问题,所以想写下这个博客,主要是便于自己对算法的理解. 来,上题. DNA Sorting Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 91599   Accepted: 36781 Descript

一个简单的特效引发的大战之移动开发中我为什么放弃jquery mobile

我本想安静的做一个美男子,可是,老板不涨工资,反而,一月不如一月. 我为什么放弃jquery mobile插件选择自己写特效? 在开发中大家都知道效率很重要,一个好的工具可以在开发中大大提升效率,工作做的越多,相应的取得的报酬也就越多,相反就是我自己了. 最近一直在一件事情上,移动线上网站测试必须符合3星,将不合格的网站调优后保证3星,方便线上推广,难免会遇见一些问题,大致为题后期会写一篇随笔总结,“移动开发中网站如何优化”.其中遇见的一个问题就是JS文件过大,CSS文件过大,之前项目一直使用的

大话JS面向对象之扩展篇 面向对象与面向过程之间的博弈论(OO Vs 过程)------(一个简单的实例引发的沉思)

一,总体概要 1,笔者浅谈 我是从学习Java编程开始接触OOP(面向对象编程),刚开始使用Java编写程序的时候感觉很别扭(面向对象式编程因为引入了类.对象.实例等概念,非常贴合人类对于世间万物的认知方式和思考方式.对于复杂的事物,人类是如何去认识.归纳.总结的?面向对象式编程就是在努力回答这个问题,而答案的核心就是两个字:抽象.所以面向对象式编程特别适合处理业务逻辑,因此被广泛应用于目前的软件开发当中.因为我们开发软件就是为了解决问题,面向对象式编程符合人类对于“问题”的认知方式),因为我早

0.28+0.34=? 一个简单小数加法引发的思考

0.28+0.34=? 我相信这个简单的加法,谁都会,肯定等于0.62嘛. 这是两个特别简单的加法,那如果我在其整数位置上加上其他的数字,或者多加几个和项,你是否还能快速算过来? 我想这时候,我们又得借助计算器了!而这,有时可能就是电脑!尤其是如果咱们借助简单程序语言来算的时候,嘿嘿,可能就不是那么回事了~ 不信你看,用javascript算的结果: 用python算的结果: 当然了,我尝试着用其他语言来试一下,结果好像并不都是这样. 其中,java只会在类型转换的时候出现奇怪的值:(当然这在我

C#用DesignSurface实现一个简单的窗体设计器

System.ComponentModel.Design.DesignSurface是为设计组件提供一个用户界面,通过它可以实现一个简单的窗体设计器. 在构建之前,我们需要引入System.Design.dll,否则会出现找不到DesignSurface的错误.  1         private void Form1_Load(object sender, EventArgs e) 2         { 3            //引用System.Deisgn.dll 4       

一个简单的WInCE(转载百度)

VS2008中开发智能设备程序的一些总结收藏1 结合前几日开发的<全国大坝基础数据库采集端>中的PDA程序开发过程,对VS2008开发智能设备上的程序做个小总结. 1         程序结构 程序中包括四个部分: 1. 系统配置 这个部分用来配置系统中的相关参数,参数包括数据库信息和串口的配置信息.这部分的主要技术是XML文件的读取和写入. 2. 数据下载 从数据库中下载数据到PDA,PDA上的保存也是使用数据库.这部分的技术主要是PDA设备上的移动数据库开发和使及用PDA连接PC数据库 3

一个由IsPrime算法引发的细节问题

//******************************* // //    2014年9月18日星期四,于宿舍撰写 //    作者:夏华林 // //******************************** 好久没有没有更新博客了,最近确实烦心事儿挺多,已经大三了,真的静下心来好好看看书了. 今天要说的,就是一个由IsPrime算法引发的细节问题,我这里说的细节,是我所认为的,若有不妥,望指正! 一个简单的IsPrime算法的实现如下: 1 bool IsPrime(int

WCF服务二:创建一个简单的WCF服务程序

在本例中,我们将实现一个简单的计算服务,提供基本的加.减.乘.除运算,通过客户端和服务端运行在同一台机器上的不同进程实现. 一.新建WCF服务 1.新建一个空白解决方案,解决方案名称为"WCFSolution". 2.解决方案右键->添加->类库项目,类库名称为CalculateWcfService. 3.创建服务契约 WCF采用基于契约的交互方式实现了服务的自制.服务契约:是相关操作的集合.契约就是双方或多方就某个关注点达成的一种共识,是一方向另一方的一种承诺.签署了某个

一个简单的记事本编辑框的实现以及搜集的一些窗口风格的预定义

这是一个简单的记事本的窗口过程 1 WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 2 3 LOCAL winRect:RECT 4 LOCAL editWidth:DWORD 5 LOCAL editHeight:DWORD 6 7 .IF uMsg==WM_DESTROY 8 invoke PostQuitMessage,NULL 9 .ELSEIF uMsg==WM_CREATE 10 ;创建一个编辑框 11