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# 一个简单的秒表引发的窗体卡死问题的更多相关文章
- 一个简单算法题引发的思考<DNA sorting>(about cin/template/new etc)
首先是昨天在北京大学oj网上看到一个简单的算法题目,虽然简单,但是如何完成一段高效.简洁.让人容易看懂的代码对于我这个基础不好,刚刚进入计算机行业的小白来说还是有意义的.而且在写代码的过程中,会发现自 ...
- 大话JS面向对象之扩展篇 面向对象与面向过程之间的博弈论(OO Vs 过程)------(一个简单的实例引发的沉思)
一,总体概要 1,笔者浅谈 我是从学习Java编程开始接触OOP(面向对象编程),刚开始使用Java编写程序的时候感觉很别扭(面向对象式编程因为引入了类.对象.实例等概念,非常贴合人类对于世间万物的认 ...
- 一个简单的特效引发的大战之移动开发中我为什么放弃jquery mobile
我本想安静的做一个美男子,可是,老板不涨工资,反而,一月不如一月. 我为什么放弃jquery mobile插件选择自己写特效? 在开发中大家都知道效率很重要,一个好的工具可以在开发中大大提升效率,工作 ...
- python 学习 : 一个简单的秒表
游戏说明:绿色数字(左边表示成功停止在整秒的次数,右边表示停止的总次数) 点击stop,如果小数点后为0,即你停止的时间是整秒数,右上方斜杠左边数字加一 把代码复制到这个网页code run he ...
- 一个简单题,引发的思索 + nyoj 1189
题目描述:第一行:给你两个数m和n,m表示有m个数,然后下一行输入m个数,每个数只能选择一次,统计共有多少种情况使得所选数的和大于等于n: 解决本题我想到了两种方法,(题目自己想的,先不考虑超时),第 ...
- C#用DesignSurface实现一个简单的窗体设计器
System.ComponentModel.Design.DesignSurface是为设计组件提供一个用户界面,通过它可以实现一个简单的窗体设计器. 在构建之前,我们需要引入System.Desig ...
- 使用Unity3D的设计思想实现一个简单的C#赛车游戏场景
最近看了看一个C#游戏开发的公开课,在该公开课中使用面向对象思想与Unity3D游戏开发思想结合的方式,对一个简单的赛车游戏场景进行了实现.原本在C#中很方便地就可以完成的一个小场景,使用Unity3 ...
- IDDD 实现领域驱动设计-一个简单业务用例的回顾和理解
上一篇:<IDDD 实现领域驱动设计-由贫血导致的失忆症> 这篇博文是对<实现领域驱动设计>第一章后半部分内容的理解. Domain Experts-领域专家 这节点内容是昨天 ...
- 一个简单的Webservice的demo,简单模拟服务
前段时间一直在学习WCF,匆匆忙忙的把<WCF全面解析>和<WCF服务编程>看了一遍,好多东西都不是很懂,又听了一下WCF分布式开发的网络教程,算是马马虎虎的明白点了.回顾了一 ...
随机推荐
- php面向对象的三大特征 封装,继承,多态
对象的主要三个特性 对象的行为:可以对 对象施加那些操作,开灯,关灯就是行为. 对象的形态:当施加那些方法是对象如何响应,颜色,尺寸,外型. 对象的表示:对象的表示就相当于身份证,具体区分在相同的行为 ...
- 用状态矩阵解决有序操作的case爆炸问题(转载)
转自http://qa.baidu.com/blog/?p=167 作者:qabloger 一. 简介 我们在测试中可能都会面对case爆炸问题.有的case组合是无序的,我们可以通过pict[1]组 ...
- OkHttp使用进阶(译自OkHttp官方教程)
没有使用过OkHttp的,可以先看OkHttp使用介绍 英文版原版地址 Recipes · square/okhttp Wiki 同步get 下载一个文件,打印他的响应头,以string形式打印响应体 ...
- Ubuntu下安装Git以及Git帮助手册【转】
转自:http://milkythinking.com/blog/2011/04/17/install_git_and_manual/ Git简介 Git是一个分布式版本控制系统,对应的是SVN.CV ...
- IntelliJ IDEA12.1.1 使用 相关记录
最近在切换ide 从eclipse平台上转移到 IDEA 其它的都比较容易,让我烦恼的是使用idea 发布工程,使用tomcat 调试 热部署.期间碰到各种问题,纠结好久了:终于成功完成了,特意记 ...
- php注册登录系统(一)-极简
序 登录注册系统是日常上网最普通的操作,我设了一个分类一步步完善注册登录系统,若有哪里错误请慧教 所用语言:php 数据库 :mysql 本次实现功能: 1.用户注册 2.用户登录 主要文件: 完整代 ...
- mysql 字符串函数
对于针对字符串位置的操作,第一个位置被标记为1. ASCII(str) 返回字符串str的 最左面字符的ASCII代码值.如果str是空字符串, 返回0.如果str是NULL,返回NULL. mysq ...
- leetcode:Intersection of Two Linked Lists(两个链表的交叉点)
Write a program to find the node at which the intersection of two singly linked lists begins. For ex ...
- 如何将SQLite数据库(dictionary.db文件)与apk文件一起发布
可以将dictionary.db文件复制到Eclipse Android工程中的res\raw目录中,如图1所示.所有在res\raw目录中的文件不会被压缩,这样可以直接提取该目录中的文件.使 用 ...
- 用Access作为后台数据库支撑,书写一个C#写入记录的案例
要想操作一个数据库,不论是那种操作,首先要做的肯定是打开数据库. 下面我们以ACCESS数据库来做例子说明如何打开一个数据库连接! 在这里我们需要用到的是: System.Data.OleDb.O ...