C# 一个简单的秒表引发的窗体卡死问题
一个秒表程序也是我的一个心病,因为一直想写这样的一个东西,但是总往GUI那边想,所以就比较怵,可能是上学的时候学MFC搞出的后遗症吧,不过当我今天想好用Win Form(话说还是第一次写win form)写这么一个东西的时候,居然so easy。
所以说,做不了不可怕,怕的是你不去做,因为你不去做,你就永远不知道你能不能做它。事实证明,大部分你犹豫能不能做的事情,实际上你都能搞定。
虽然成功实现了一个秒表的简单功能,即开始计时和停止。但是却引发了一个关于win form和C#线程的问题。
下面一个一个来,先说一下秒表的类实现
namespaceUtils{publicclassTime{privateint_minute;privateint_second;privatebool_flag;//线程标识privateThread_TimingThread=null;publicTime(){this._minute=0;this._second=0;this._flag=true;}///<summary>///开始计时///</summary>publicvoidStart(){if(_TimingThread==null||!this._flag){this._flag=true;_TimingThread=newThread(newThreadStart(AddSecond));_TimingThread.Start();}}///<summary>///线程执行方法///</summary>privatevoidAddSecond(){while(_flag){Thread.Sleep(1000);if(this._second==59){this._minute++;this._second=0;}else{this._second++;}}}///<summary>///格式化显示计时结果///</summary>///<returns></returns>publicstringFormatTimeResult(){stringminute=string.Empty;stringsecond=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();}returnminute+":"+second;}///<summary>///停止///</summary>publicvoidStop(){this._flag=false;}///<summary>///归0操作///</summary>publicvoidZero(){this._minute=0;this._second=0;}}}
秒表的实现还是比较简单的,感觉这样写,也方便以后做扩展。
下面说说win form方面
窗体就是这样,一个label,两个button
最开始,我写了这样一段代码
publicpartialclassForm1:Form{privateTimemTime=null;privateThreadmDisplayThread=null;publicForm1(){InitializeComponent();mTime=newTime();//实例化秒表类}privatevoidbutton_start_Click(objectsender,EventArgse){mTime.Start();mDisplayThread=newThread(newThreadStart(DisplayCurrentTime));mDisplayThread.Start();button_start.Enabled=false;}publicvoidDisplayCurrentTime(){while(true){Thread.Sleep(1000);label_Time.Text=mTime.FormatTimeResult();Console.WriteLine("{0}",mTime.FormatTimeResult());}}privatevoidbutton_stop_Click(objectsender,EventArgse){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去联合实现。下面看看实现部分
publicpartialclassForm1:Form{privateTimemTime=null;privateThreadmDisplayThread=null;publicdelegatevoidUpdateLabel();//声明一个委托publicUpdateLabelupdateLabel;//定义一个委托publicForm1(){InitializeComponent();mTime=newTime();updateLabel=newUpdateLabel(UpdateTime);}privatevoidbutton_start_Click(objectsender,EventArgse){mTime.Start();mDisplayThread=newThread(newThreadStart(DisplayTimeFunc));mDisplayThread.Start();button_start.Enabled=false;}///<summary>///线程执行方法///</summary>publicvoidDisplayTimeFunc(){while(true){Thread.Sleep(1000);this.Invoke(this.updateLabel);}}///<summary>///单独对Label进行刷新///</summary>publicvoidUpdateTime(){label_Time.Text=mTime.FormatTimeResult();}privatevoidbutton_stop_Click(objectsender,EventArgse){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 这个线程做了终止的动作。
所以我们还需要添加以下动作
privatevoidForm1_FormClosing(objectsender,FormClosingEventArgse){mDisplayThread.Abort();}
这样就完整了,在关闭Form1窗体之前,先把线程终止。
做这个小东西的时候居然连带着让我了解了一些委托和Control.Invoke以及线程的知识点。我会找个时间好好把这部分看看的,争取能总结点什么出来。
新浪微博:http://weibo.com/zhouhongyu1989欢迎围观~!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。