CSDN第一期总结之三:Thread的问题(转)
C#是一门支持多线程的语言,因此线程的使用也是比较常见的。由于线程的知识在Win32编程的时候已经说得过多,所以在.Net中很少介绍这部分(可能.Net不觉得这部分是它所特有的)。
那么线程相关的问题大致有如下四类(这篇文章只讨论单线程、单线程与UI线程这两方面的问题)。
问题一,线程的基本操作,例如:暂停、继续、停止等;
问题二,如何向线程传递参数或者从中得到其返回值;
问题三,如何使线程所占用的CPU不要老是百分之百;
最后一个,也是问题最多的,就是如何在子线程来控制UI中的控件,换句话说,就是在线程中控制窗体某些控件的显示。
对于问题一,我不建议使用Thread类提供的Suspend、Resume以及Abort这三个方法,前两个有问题,好像在VS05已经屏蔽这两个方法;对于Abort来说,除了资源没有得到及时释放外,有时候会出现异常。如何做呢,通过设置开关变量来完成。
对于问题二,我不建议使用静态成员来完成,仅仅为了线程而破坏类的封装有些得不偿失。那如何做呢,通过创建单独的线程类来完成。
对于问题三来说,造成这个原因是由于线程中进行不间断的循环操作,从而使CPU完全被子线程占有。那么处理此类问题,其实很简单,在适当的位置调用Thread.Sleep(20)来释放所占有CPU资源,不要小看这20毫秒的睡眠,它的作用可是巨大的,可以使其他线程得到CPU资源,从而使你的CPU使用效率降下来。
看完前面的三个问题的解释,对于如何做似乎没有给出一个明确的答案,为了更好地说明如何解决这三个问题,我用一个比较完整的例子展现给大家,代码如下。
//--------------------------- Sub-thread class ---------------------------------------
//------------------------------------------------------------------------------------
//---File: clsSubThread
//---Description: The sub-thread template class file
//---Author: Knight
//---Date: Aug.21, 2006
//------------------------------------------------------------------------------------
//---------------------------{Sub-thread class}---------------------------------------
namespace ThreadTemplate
{
using System;
using System.Threading;
using System.IO;
/// <summary>
/// Summary description for clsSubThread.
/// </summary>
public class clsSubThread:IDisposable
{
private Thread thdSubThread = null;
private Mutex mUnique = new Mutex();
private bool blnIsStopped;
private bool blnSuspended;
private bool blnStarted;
private int nStartNum;
public bool IsStopped
{
get{ return blnIsStopped; }
}
public bool IsSuspended
{
get{ return blnSuspended; }
}
public int ReturnValue
{
get{ return nStartNum;}
}
public clsSubThread( int StartNum )
{
//
// TODO: Add constructor logic here
//
blnIsStopped = true;
blnSuspended = false;
blnStarted = false;
nStartNum = StartNum;
}
/// <summary>
/// Start sub-thread
/// </summary>
public void Start()
{
if( !blnStarted )
{
thdSubThread = new Thread( new ThreadStart( SubThread ) );
blnIsStopped = false;
blnStarted = true;
thdSubThread.Start();
}
}
/// <summary>
/// Thread entry function
/// </summary>
private void SubThread()
{
do
{
// Wait for resume-command if got suspend-command here
mUnique.WaitOne();
mUnique.ReleaseMutex();
nStartNum++;
Thread.Sleep(1000); // Release CPU here
}while( blnIsStopped == false );
}
/// <summary>
/// Suspend sub-thread
/// </summary>
public void Suspend()
{
if( blnStarted && !blnSuspended )
{
blnSuspended = true;
mUnique.WaitOne();
}
}
/// <summary>
/// Resume sub-thread
/// </summary>
public void Resume()
{
if( blnStarted && blnSuspended )
{
blnSuspended = false;
mUnique.ReleaseMutex();
}
}
/// <summary>
/// Stop sub-thread
/// </summary>
public void Stop()
{
if( blnStarted )
{
if( blnSuspended )
Resume();
blnStarted = false;
blnIsStopped = true;
thdSubThread.Join();
}
}
#region IDisposable Members
/// <summary>
/// Class resources dispose here
/// </summary>
public void Dispose()
{
// TODO: Add clsSubThread.Dispose implementation
Stop();//Stop thread first
GC.SuppressFinalize( this );
}
#endregion
}
}
那么对于调用呢,就非常简单了,如下:
// Create new sub-thread object with parameters
clsSubThread mySubThread = new clsSubThread( 5 );
mySubThread.Start();//Start thread
Thread.Sleep( 2000 );
mySubThread.Suspend();//Suspend thread
Thread.Sleep( 2000 );
mySubThread.Resume();//Resume thread
Thread.Sleep( 2000 );
mySubThread.Stop();//Stop thread
//Get thread's return value
Debug.WriteLine( mySubThread.ReturnValue );
//Release sub-thread object
mySubThread.Dispose();
在回过头来看看前面所说的三个问题。
对于问题一来说,首先需要局部成员的支持,那么
private Mutex mUnique = new Mutex();
private bool blnIsStopped;
private bool blnSuspended;
private bool blnStarted;
光看成员名称,估计大家都已经猜出其代表的意思。接下来需要修改线程入口函数,要是这些开关变量能发挥作用,那么看看SubThread这个函数。
/// <summary>
/// Thread entry function
/// </summary>
private void SubThread()
{
do
{
// Wait for resume-command if got suspend-command here
mUnique.WaitOne();
mUnique.ReleaseMutex();
nStartNum++;
Thread.Sleep(1000);
}while( blnIsStopped == false );
}
函数比较简单,不到十句,可能对于“blnIsStopped == false”这个判断来说,大家还比较好理解,这是一个普通的判断,如果当前Stop开关打开了,就停止循环;否则一直循环。
大家比较迷惑的可能是如下这两句:
mUnique.WaitOne();
mUnique.ReleaseMutex();
这两句的目的是为了使线程在Suspend操作的时候能发挥效果,为了解释这两句,需要结合Suspend和Resume这两个方法,它俩的代码如下。
/// <summary>
/// Suspend sub-thread
/// </summary>
public void Suspend()
{
if( blnStarted && !blnSuspended )
{
blnSuspended = true;
mUnique.WaitOne();
}
}
/// <summary>
/// Resume sub-thread
/// </summary>
public void Resume()
{
if( blnStarted && blnSuspended )
{
blnSuspended = false;
mUnique.ReleaseMutex();
}
}
为了更好地说明,还需要先简单说说Mutex类型。对于此类型对象,当调用对象的WaitOne之后,如果此时没有其他线程对它使用的时候,就立刻获得信号量,继续执行代码;当再调用ReleaseMutex之前,如果再调用对象的WaitOne方法,就会一直等待,直到获得信号量的调用ReleaseMutex来进行释放。这就好比卫生间的使用,如果没有人使用则可以直接使用,否则只有等待。
明白了这一点后,再来解释这两句所能出现的现象。
mUnique.WaitOne();
mUnique.ReleaseMutex();
当在线程函数中,执行到“mUnique.WaitOne();”这一句的时候,如果此时外界没有发送Suspend消息,也就是信号量没有被占用,那么这一句可以立刻返回。那么为什么要紧接着释放呢,因为不能总占着信号量,立即释放信号量是避免在发送Suspend命令的时候出现等待;如果此时外界已经发送了Suspend消息,也就是说信号量已经被占用,此时“mUnique.WaitOne();”不能立刻返回,需要等到信号量被释放才能继续进行,也就是需要调用Resume的时候,“mUnique.WaitOne();”才能获得信号量进行继续执行。这样才能达到真正意义上的Suspend和Resume。
至于线程的Start和Stop来说,相对比较简单,这里我就不多说了。
现在再来分析一下问题二,其实例子比较明显,是通过构造函数和属性来完成参数和返回值,这一点我也不多说了。如果线程参数比较多的话,可以考虑属性来完成,类似于返回值。
问题三,我就更不用多说了。有人说了,如果子线程中的循环不能睡眠怎么办,因为睡眠的话,有时会造成数据丢失,这方面的可以借鉴前面Suspend的做法,如果更复杂,则牵扯到多线程的同步问题,这部分我会稍后单独写一篇文章。
前三个问题解决了,该说说最常见的问题,如何在子线程中控制窗体控件。这也是写线程方面程序经常遇到的,其实我以前写过两篇文章,都对这方面做了部分介绍。那么大家如果有时间的话,不妨去看看。
http://blog.csdn.net/knight94/archive/2006/03/16/626584.aspx
http://blog.csdn.net/knight94/archive/2006/05/27/757351.aspx
首先说说,为什么不能直接在子线程中操纵UI呢。原因在于子线程和UI线程属于不同的上下文,换句比较通俗的话说,就好比两个人在不同的房间里一样,那么要你直接操作另一个房间里的东西,恐怕不行罢,那么对于子线程来说也一样,不能直接操作UI线程中的对象。
那么如何在子线程中操纵UI线程中的对象呢,.Net提供了Invoke和BeginInvoke这两种方法。简单地说,就是子线程发消息让UI线程来完成相应的操作。
这两个方法有什么区别,这在我以前的文章已经说过了,Invoke需要等到所调函数的返回,而BeginInvoke则不需要。
用这两个方法需要注意的,有如下三点:
第一个是由于Invoke和BeginInvoke属于Control类型的成员方法,因此调用的时候,需要得到Control类型的对象才能触发,也就是说你要触发窗体做什么操作或者窗体上某个控件做什么操作,需要把窗体对象或者控件对象传递到线程中。
第二个,对于Invoke和BeginInvoke接受的参数属于一个delegate类型,我在以前的文章中使用的是MethodInvoker,这是.Net自带的一个delegate类型,而并不意味着在使用Invoke或者BeginInvoke的时候只能用它。参看我给的第二篇文章(《如何弹出一个模式窗口来显示进度条》),会有很多不同的delegate定义。
最后一个,使用Invoke和BeginInvoke有个需要注意的,就是当子线程在Form_Load开启的时候,会遇到异常,这是因为触发Invoke的对象还没有完全初始化完毕。处理此类问题,在开启线程之前显式的调用“this.Show();”,来使窗体显示在线程开启之前。如果此时只是开启线程来初始化显示数据,那我建议你不要使用子线程,用Splash窗体的效果可能更好。这方面可以参看如下的例子。
http://www.syncfusion.com/FAQ/WindowsForms/FAQ_c95c.aspx#q621q
线程的四个相关问题已经说完了,这篇文章只说了单线程,以及单线程与UI线程交互的问题。其中涉及到的方法不一定是唯一的,因为.Net还提供了其他类来扶助线程操作,这里就不一一罗列。至于多线程之间的同步,我会稍后专门写篇文章进行描述。
CSDN第一期总结之三:Thread的问题(转)的更多相关文章
- 成功的背后!(给所有IT人)----转载:来自CSDN第一名博主
转载:来自CSDN第一名博主:http://blog.csdn.net/phphot/article/details/2187505 放在这里激励你我! 正文: 成功的背后,有着许多不为人知的故事,而 ...
- IT资源关东煮第一期【来源于网络】
IT资源关东煮第一期[来源于网络] 地址:http://geek.csdn.net/news/detail/128222
- _00020 妳那伊抹微笑_谁的异常最诡异第一期之 SqlServer RSA premaster secret error
博文作者:妳那伊抹微笑 博客地址:http://blog.csdn.net/u012185296 博文标题:_00020 妳那伊抹微笑_谁的异常最诡异第一期之 SqlServer RSA premas ...
- 豪斯课堂K先生全套教程淘宝设计美工第一期+第四期教程(无水印)
第一期课程包括 <配色如此简单> <配色的流程><对称之美>第二期课程包括 <字体的气质及组合><平衡及构图形式><信息的筛选与图片的 ...
- 复旦大学EWP菁英女性课程(复旦卓越女性课程改版后第一期) _复旦大学、女性课程、高级研修班、心理学、EWP_培训通课程
复旦大学EWP菁英女性课程(复旦卓越女性课程改版后第一期) _复旦大学.女性课程.高级研修班.心理学.EWP_培训通课程 复旦大学EWP菁英女性课程(复旦卓越女性课程改版后第一期) 学 ...
- MobileOA第一期总结
MobileOA第一期总结 前段时间一直没有更新博客,好想给自己找个借口---恩,我还是多找几个吧.毕业论文.毕业照,再感伤一下,出去玩一下,不知不觉就过去几个月了.然后上个月底才重新回到学习之路,从 ...
- 创建多线程的第一种方式——创建Thread子类和重写run方法
创建多线程的第一种方式——创建Thread子类和重写run方法: 第二种方式——实现Runnable接口,实现类传参给父类Thread类构造方法创建线程: 第一种方式创建Thread子类和重写run方 ...
- [转]ZooKeeper学习第一期---Zookeeper简单介绍
ZooKeeper学习第一期---Zookeeper简单介绍 http://www.cnblogs.com/sunddenly/p/4033574.html 一.分布式协调技术 在给大家介绍ZooKe ...
- 【每天五分钟大数据-第一期】 伪分布式+Hadoopstreaming
说在前面 之前一段时间想着把 LeetCode 每个专题完结之后,就开始着手大数据和算法的内容. 想来想去,还是应该穿插着一起做起来. 毕竟,如果只写一类的话,如果遇到其他方面,一定会遗漏一些重要的点 ...
随机推荐
- bzoj 4007 树形dp
题目大意 脸哥最近来到了一个神奇的王国,王国里的公民每个公民有两个下属或者没有下属,这种关系刚好组成一个 n 层的完全二叉树.公民 i 的下属是 2 * i 和 2 * i +1.最下层的公民即叶子节 ...
- 【CF659E】New Reform(图的联通,环)
分析转载自http://blog.csdn.net/yukizzz/article/details/51029628 题意: 给定n个点和m条双向边,将双向边改为单向边,问无法到达的顶点最少有多少个? ...
- CSS实现Footer固定底部,超过一屏自动撑开
方法一:给html.body都设置100%的高度,确定body下内容设置min-height有效,然后设置主体部分min-height为100%,此时若没有header.footer则刚好完美占满全屏 ...
- 一个老忘且非常有用的jquery动画方法 网页上卷
$('html,body').animate({scrollTop:800+'px'},500) //网页上卷800像素 在半秒之内
- React-Native Navigator 过渡动画卡顿的解决方案
在RN0.44版本之前,路由导航跳转几乎是使用的是Navigator组件,在0.44版本以后就不推荐使用了,官方推荐的是react-navigation,当然还是可以在废弃的库中找到: import ...
- ASP.NET HttpContext类
IHttpHandler 接口 定义 ASP.NET 以异步方式处理使用自定义 HTTP 处理程序的 HTTP Web 请求而实现的协定 封装http请求信息 HttpContext.Curren ...
- webstrom配置一键修复ESLint的报错
因为项目本身有用eslint,而我这边没用,我这边提交上去别人update后就会提示很多eslint的格式错误提示,所以就在该项目里使用了eslint. 发现一般有两种安装方式,我使用的是webstr ...
- spring的自动装配Bean与自动检测Bean
spring可以通过编写XML来配置Bean,也可以通过使用spring的注解来装配Bean. 1.自动装配与自动检测: 自动装配:让spring自动识别如何装配bean的依赖关系,减少对<pr ...
- Java中使用new Date()和System.currentTimeMillis()获取当前时间戳的区别(转)(Java进阶-性能提升)
在开发过程中,通常很多人都习惯使用new Date()来获取当前时间,使用起来也比较方便,同时还可以获取与当前时间有关的各方面信息,例如获取小时,分钟等等,而且还可以格式化输出,包含的信息是比较丰富的 ...
- arcgis andriod Edit features
来自:https://developers.arcgis.com/android/guide/edit-features.htm#ESRI_SECTION1_56C60DB71AF941E98668A ...