C# 一个简单的秒表引发的窗体卡死问题
2014-06-13 14:58
721 查看
一个秒表程序也是我的一个心病,因为一直想写这样的一个东西,但是总往GUI那边想,所以就比较怵,可能是上学的时候学MFC搞出的后遗症吧,不过当我今天想好用Win Form(话说还是第一次写win form)写这么一个东西的时候,居然so easy。
所以说,做不了不可怕,怕的是你不去做,因为你不去做,你就永远不知道你能不能做它。事实证明,大部分你犹豫能不能做的事情,实际上你都能搞定。
虽然成功实现了一个秒表的简单功能,即开始计时和停止。但是却引发了一个关于win form和C#线程的问题。
下面一个一个来,先说一下秒表的类实现
下面说说win form方面
窗体就是这样,一个label,两个button
最开始,我写了这样一段代码
然而,之后却报一个这样的错误: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的线程里调用,不能在以外的线程被调用。而上面的
解决办法是用delegate(委托)加上control.Invoke去联合实现。下面看看实现部分
回头想想,其实思路也比较简单,就是先将更改控件属性的操作放在一个方法里,然后写个委托,再写个线程,在线程的执行方法中调用这个委托就OK啦。
不过到这还不算全完,还有一个小问题,就是当我计时之后,想要关闭这个窗体的时候,发现又开始报错了:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
研究了一下发现了出现此问题的原因,就是我们“上完厕所没有擦PP”,上面的代码中没有一个操作是对 mDisplayThread 这个线程做了终止的动作。
所以我们还需要添加以下动作
做这个小东西的时候居然连带着让我了解了一些委托和Control.Invoke以及线程的知识点。我会找个时间好好把这部分看看的,争取能总结点什么出来。
新浪微博:http://weibo.com/zhouhongyu1989 欢迎围观~!
所以说,做不了不可怕,怕的是你不去做,因为你不去做,你就永远不知道你能不能做它。事实证明,大部分你犹豫能不能做的事情,实际上你都能搞定。
虽然成功实现了一个秒表的简单功能,即开始计时和停止。但是却引发了一个关于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 || !this._flag) { this._flag = true; _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(); 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以及线程的知识点。我会找个时间好好把这部分看看的,争取能总结点什么出来。
新浪微博:http://weibo.com/zhouhongyu1989 欢迎围观~!
相关文章推荐
- C# 一个简单的秒表引发的窗体卡死问题
- WinForm:一个登陆窗体引发的问题系列(一):最简单的登陆窗口
- WinForm:一个登陆窗体引发的问题系列(二):用户名文本框的输入限制
- WinForm:一个登陆窗体引发的问题系列(六):Textbox的记忆功能
- C# 在一个窗体中调用另一个窗体的控件可能出现的问题
- 浅谈C#跨线程调用窗体控件(比如TextBox)引发的线程安全问题
- C#用DesignSurface实现一个简单的窗体设计器
- WinForm:一个登陆窗体引发的问题系列(三):密码文本框的输入限制
- 强大的C# Expression在一个函数求导问题中的简单运用
- C#处理猜拳问题的简单实例(非窗体)
- C#用DesignSurface实现一个简单的窗体设计器
- ~~把我搞晕了!一个简单的C#语法问题
- c#中子线程控制进度条的一个简单例子(多线程问题)
- 强大的C# Expression在一个函数求导问题中的简单运用
- 由一个简单的String c=a+b的Java问题引发一点想法
- 一个关于C#中一次关闭多个子窗体的问题
- WinForm:一个登陆窗体引发的问题系列(五):窗口跳转
- 由一个简单的String c=a+b的Java问题引发一点想法 推荐
- C#中一个窗体传递另一个窗体的值和sting类型到double类型的转换两个问题
- C#.NET常见问题(FAQ)-程序如何把窗体文件从从一个项目中复制到另一个项目