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还提供了其他类来扶助线程操作,这里就不一一罗列。至于多线程之间的同步,我会稍后专门写篇文章进行描述。

 

 

 

 

 

 

 

 

如何在子线程中操作窗体上的控件

 

一般来说,直接在子线程中对窗体上的控件操作是会出现异常,这是由于子线程和运行窗体的线程是不同的空间,因此想要在子线程来操作窗体上的控件,是不可能简单的通过控件对象名来操作,但不是说不能进行操作,微软提供了Invoke的方法,其作用就是让子线程告诉窗体线程来完成相应的控件操作。

现在用一个用线程控制的进程条来说明,大致的步骤如下:

1. 创建Invoke函数,大致如下:

/// <summary>

/// Delegate function to be invoked by main thread

/// </summary>

private void InvokeFun()

{

if( prgBar.Value < 100 )

prgBar.Value = prgBar.Value + 1;

}

2. 子线程入口函数:

/// <summary>

/// Thread function interface

/// </summary>

private void ThreadFun()

{

//Create invoke method by specific function

MethodInvoker mi = new MethodInvoker( this.InvokeFun );

for( int i = 0; i < 100; i++ )

{

this.BeginInvoke( mi );

Thread.Sleep( 100 );

}

}

3. 创建子线程:

Thread thdProcess = new Thread( new ThreadStart( ThreadFun ) );

thdProcess.Start();

备注:

using System.Threading;

private System.Windows.Forms.ProgressBar prgBar;

运行后的效果如下图所示:

 

 

 

 

如何弹出一个模式窗口来显示进度条

最近看了好多人问这方面的问题,以前我也写过一篇blog,里面说了如何在子线程中控制进度条。但目前大多数环境,需要弹出模式窗口,来显示进度条,那么只需要在原先的基础上稍作修改即可。

首先是进度条窗体,需要在上面添加进度条,然后去掉ControlBox。除此外,还要增加一个方法,用来控制进度条的增加幅度,具体如下:

/// <summary>

/// Increase process bar

/// </summary>

/// <param name="nValue">the value increased</param>

/// <returns></returns>

public bool Increase( int nValue )

{

if( nValue > 0 )

{

if( prcBar.Value + nValue < prcBar.Maximum )

{

prcBar.Value += nValue;

return true;

}

else

{

prcBar.Value = prcBar.Maximum;

this.Close();

return false;

}

}

return false;

}

接着就是主窗体了,如何进行操作了,首先需要定义两个私有成员,一个委托。其中一个私有成员是保存当前进度条窗体对象,另一个是保存委托方法(即增加进度条尺度),具体如下:

private frmProcessBar myProcessBar = null;

private delegate bool IncreaseHandle( int nValue );

private IncreaseHandle myIncrease = null;

接着要在主窗体中提供函数来打开进度条窗体,如下:

/// <summary>

/// Open process bar window

/// </summary>

private void ShowProcessBar()

{

myProcessBar = new frmProcessBar();

// Init increase event

myIncrease = new IncreaseHandle( myProcessBar.Increase );

myProcessBar.ShowDialog();

myProcessBar = null;

}

那么现在就可以开始创建线程来运行,具体如下:

/// <summary>

/// Sub thread function

/// </summary>

private void ThreadFun()

{

MethodInvoker mi = new MethodInvoker( ShowProcessBar );

this.BeginInvoke( mi );

Thread.Sleep( 1000 );//Sleep a while to show window

bool blnIncreased = false;

object objReturn = null;

do

{

Thread.Sleep( 50 );

objReturn = this.Invoke( this.myIncrease,

new object[]{ 2 } );

blnIncreased = (bool)objReturn ;

}

while( blnIncreased );

}

注意以上,在打开进度条窗体和增加进度条进度的时候,一个用的是BeginInvoke,一个是Invoke,这里的区别是BeginInvoke不需要等待方法运行完毕,而Invoke是要等待方法运行完毕。还有一点,此处用返回值来判断进度条是否到头了,如果需要有其他的控制,可以类似前面的方法来进行扩展。

启动线程,可以如下:

Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );

thdSub.Start();

这样,一个用模式打开进度条窗体就做完了。

发表于 @ 2006年05月27日 10:43:00 | 评论( 55 ) | 编辑| 举报| 收藏

旧一篇:程序以及窗体运行的唯一性汇总 | 新一篇:如何去写一个.Net程序

查看最新精华文章 请访问博客首页相关文章
很久没发代码了,今天来发些C#代码
如何弹出一个模式窗口来显示进度条
VS2005字符串资源不能识别转义字符
如何弹出一个模式窗口来显示进度条
如何弹出一个模式窗口来显示进度条
子线程控制主线程中UI显示
c#模态进度条
c#模态进度条

Tuff 发表于2006年5月29日 22:11:00  IP:举报回复删除
未处理 System.InvalidOperationException
Message="在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。"
Source="System.Windows.Forms"
knight94 发表于2006年5月30日 7:44:00  IP:举报回复删除
to Tuff
出现这类问题,主要是窗体还没形成,就调用Invoke或者BeginInvoke方法。
哪个方法产生的异常,如果是第二个,你把Thread.Sleep时间加长一些,或者在其Form_load时间中尽快的显示出来,即用“this.Show()”。
violence 发表于2006年7月31日 16:07:00  IP:举报回复删除
怎么改变线程里的某个被用的变量的量.
knight94 发表于2006年8月1日 17:49:00  IP:举报回复删除
参看我的另外一篇文章,关于线程类的,
地址:
http://blog.csdn.net/knight94/archive/2006/03/21/631238.aspx
4G 发表于2006年8月4日 11:31:00  IP:举报回复删除
frmProcessBar是什么类
knight94 发表于2006年8月4日 11:54:00  IP:举报回复删除
to frmProcessBar是什么类
显示进度条的窗体类
tianjj 发表于2006年8月14日 14:18:00  IP:举报回复删除
为什么要用线程,请指教。
是菜鸟
tianjj 发表于2006年8月14日 14:36:00  IP:举报回复删除
用Timer是不是也可以。
请教两者的区别。
knight94 发表于2006年8月14日 14:49:00  IP:举报回复删除
to tianjj
用timer操作进度条没有什么问题,但是和数据导入之类操作的交互性就比较差了。
tianjj 发表于2006年8月14日 15:46:00  IP:举报回复删除
OK,谢谢knight94
tianjj 发表于2006年8月14日 15:58:00  IP:举报回复删除
还有一件事请教:
this.BeginInvoke(mi);
在vsnet为什么不能智能显示。
knight94 发表于2006年8月14日 17:47:00  IP:举报回复删除
to tianjj
你所谓的智能显示具体是什么意思
tianjj 发表于2006年8月15日 10:42:00  IP:举报回复删除
this.BeginInvoke(mi);
==========
OK,
解决了,是.net1.1 和2.0下都调试成功。
如果在frmProcessBar做一个随这着进度条的百分比,是否还要有一个Invoke 线程。
knight94 发表于2006年8月15日 12:01:00  IP:举报回复删除
to tianjj
至于百分比之类的,属于细节问题,
我只是给了一个方法,怎么在我的基础上进行扩展属于你的工作。
我相信你会做得更漂亮。
damofengbo 发表于2006年8月27日 16:37:00  IP:举报回复删除
MethodInvoker mi = new MethodInvoker (ShowProcessBar );
this.BeginInvoke( mi );
出错:ShowProcessBar()
与委托“void System.Windows.Forms.MethodInvoker()”不匹配
damofengbo 发表于2006年8月27日 16:32:00  IP:举报回复删除
MethodInvoker mi = new MethodInvoker (ShowProcessBar );
this.BeginInvoke( mi );
出错:ShowProcessBar()
与委托“void System.Windows.Forms.MethodInvoker()”不匹配
cyf142857 发表于2010年3月22日 12:00:29  IP:举报回复删除
回复 damofengbo:说明你写的委托和函数不匹配,包括参数或者返回值不匹配
damofengbo 发表于2006年8月27日 17:41:00  IP:举报回复删除
问题:
主窗体类()
{控件1;A a;B b}
类A()
{B b; 方法F1(){b.F2(参数)}}
类B()
{控件1 方法F2(参数){访问控件1}}
线程函数里面调用a.F1()访问控件1,
这里好像是线程里面访问主界面的控件问题
请问我的方法F1里面该怎么写,
F1()
{
object[] pList = {参数};
MethodInvoker mi = new MethodInvoker(b.F2);
b.控件1.BeginInvoke(mi,pList);
}
knight94 发表于2006年8月27日 18:39:00  IP:举报回复删除
to damofengbo
MethodInvoker只是一个比较简单的委托,原型如下
public delegate void MethodInvoker();
如果你要使用MethodInvoker来定义对象,首先要看看是否符合上面的样式。
如果不符合的话,可以进行扩展,例如:
public delegate returntype myMethodInvoker( yourParam );
myMethodInvoker mi = new myMethodInvoker( .. );
yourControl.BeginInvoke( mi, new object[]{..} );
damofengbo 发表于2006年8月28日 19:41:00  IP:举报回复删除
谢谢
dlzhangln 发表于2006年9月21日 16:21:00  IP:举报回复删除
帅,你就是我偶像
User 发表于2006年9月22日 11:51:00  IP:举报回复删除
学习:
我如果有如下:
// System.Data.SqlClient.SqlConnection m_conn = null;
// m_conn = new System.Data.SqlClient.SqlConnection();
// m_conn.ConnectionString = "Data Source=192.168.0.1; Initial Catalog=MaxData; User Id=sa; Password=123456;";
// m_conn.Open();
通过进度条来显示速度,该放到您的代码的什么位置呢?
knight94 发表于2006年9月22日 12:13:00  IP:举报回复删除
to user
对于你所说的,并不是很好控制,因为对于connection的open操作,你无法判断要用多少时间,因此进度条的信息只能是模拟,无法去精确控制。
线程中大致如下:
//打开进度条窗体
//用begininvoke去触发窗体来打开connection
while( !blnFinished )
{
//按照一定步频修改进度条
Thread.Sleep( 20 );
}
//关闭进度条窗体
以上是在线程函数中进行的操作。
那么对于连接可以如下:
private bool blnFinished = false;
private void ConnectDB()
{
//Open connection here
blnFinished = true;//Set signal here
}
User 发表于2006年10月11日 18:39:00  IP:举报回复删除
那么对于从数据库检索数据,能否用进度条来控制呢?
knight94 发表于2006年10月11日 18:44:00  IP:举报回复删除
to User
如果是用DataAdapter.Fill的话,很难在进度条上体现;
如果是用DataReader来逐条读取的话,是可以的。
yanam 发表于2006年10月13日 16:56:00  IP:举报回复删除
IncreaseHandle myIncrease = new IncreaseHandle(myProcessBar.Increase);
为何我在这个地方会提示
实例方法的委托不能具有空“this”。
我用的是。net2.0
knight94 发表于2006年10月13日 18:34:00  IP:举报回复删除
to yanam
我试了一下,并没有发生类似的问题。
而且这点儿来说,1.1和2.0没有区别。
Jason_mf 发表于2006年10月14日 16:57:00  IP:举报回复删除
private void ThreadFun()中的
object objReturn = null是什么?
knight94 发表于2006年10月14日 19:11:00  IP:举报回复删除
to Jason_mf
我的例子中使用的Delegate是有返回值的,而返回值需要通过Object才能接收。
所以objReturn是接收委托函数返回值用的。
zyc21st 发表于2006年10月22日 8:15:00  IP:举报回复删除
我将启动线程的语句
Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );
thdSub.Start();
放在主窗体的一个方法体中执行,thdSub.Start();后面还有其他的语句,为什么必须该方法执行完后,进度条窗体才能启动?
不能执行完thdSub.Start();就启动进度条窗体?
knight94 发表于2006年10月22日 9:49:00  IP:举报回复删除
to zyc21st
你如果想在thdSub.Start()后立刻开启线程,可以在之后先加上
Thread.Sleep(20);//Run sub-thread
即可。
zyc21st 发表于2006年10月22日 10:53:00  IP:举报回复删除
我现在想实现这样一个效果:
在主窗口中点击按钮Download一个文件,并打开进度条等待
private void button1_Click(object sender, System.EventArgs e)
{
Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );
thdSub.Start();
Thread.Sleep(20);
ftp.Download();
}
现在好像必须ftp.Download();执行完成之后,进度窗体才能显示,我现在想thdSub.Start();之后进度窗体显示,进度条开始滚动,同时主窗体在执行ftp.Download();下载文件,这个地方应该怎么写?谢谢你了
knight94 发表于2006年10月31日 12:54:00  IP:举报回复删除
to webber
你如何写的,
这里需要注意两点,
1、如果窗体用ShowDialog打开,不要用Invoke,而要用BeginInvoke;
2、如果窗体用Show打开,不要把窗体定义为局部变量,把它改成成员。
webber 发表于2006年10月31日 11:48:00  IP:举报回复删除
我在调用ShowProcessBar()的时候用的是Invoke方法(自己写了一个委托),但是当弹出进度条窗体的时候就没有反应了,因为什么啊?
webber 发表于2006年10月31日 13:37:00  IP:举报回复删除
谢谢您,还有一个问题。BeginInvoke中的的委托是必须是MethodInvoker的吗?
yanam 发表于2006年10月31日 13:31:00  IP:举报回复删除
我调用的时候有时很正常的运行,但是有时弹出这个窗口就整个程序都没反应了,这是怎么回事啊?
knight94 发表于2006年11月1日 10:22:00  IP:举报回复删除
to yanam
仔细检查你的程序,以我的程序进行参考,对比细节处,是否处理正确。
to webber
不一定是MethodInvoker,Invoke或者BeginInvoke只是执行一个委托函数而已,并不要求委托函数类型具体是什么,这方面你可以参看
http://blog.csdn.net/Knight94/archive/2006/08/24/1111267.aspx
yanam 发表于2006年11月2日 15:04:00  IP:举报回复删除
knight94 你好,这个进度条我是这样调用的
private void ShareSelect_Load(object sender, EventArgs e)
{
Thread thdSub = new Thread(new ThreadStart(ThreadFun));
thdSub.Start();
initData();//填充datagrid
}
如果用线程调用initdata,会出错,如何才能initdata完成就进度条完成100关闭呢?
谢谢,刚学c#,很多东西不熟悉
knight94 发表于2006年11月2日 17:05:00  IP:举报回复删除
to yanam
如果比较正确地完成你的功能,需要把initData这个函数放到线程中去做,并写在初始化datagrid时候,动态改变进度条的值。
那么其中涉及到的invoke或者begininvoke有三个方面
1、进度条的窗体模态显示;
2、进度条的变动;
3、datagrid的操作。
那么这些都可以参照我给的方法去完成即可。
vessel 发表于2006年11月13日 11:11:00  IP:举报回复删除
knight94
你好,我想实现调用一个dll函数,这个函数调用会占比较长的时间,所以显示进度条。
希望效果:
开始调用该函数时显示进度条窗体,并不断让进度条滚动,函数调用好后关闭进度条窗体,由于不能确定函数调用时间,把进度条style设成Marquee就可以了。
具体该怎么做?谢谢!
knight94 发表于2006年11月13日 15:26:00  IP:举报回复删除
to vessel
文章控制进度条的显示是在“ThreadFun”这个函数中,那么你可以对它进行扩展,让调用函数和进度条的显示进行同步。
zhouxiaoming 发表于2008年3月14日 15:33:04  IP:举报回复删除
hi,你好
我还是没搞好,请帮我看看啊。
具体如下:
一个费时的操作A,在执行的时候会自动弹出几个窗体;
一个记录操作A进度的全局变量i,用来显示执行进度;
method A()
{
...
Thread thdSub = new Thread(new ThreadStart(ThreadFun));
thdSub.Start();
...(省略的用method B表示吧)
}
method B()
{
...
i++;
...
}
//显示进度
private void ThreadFun()
{
MethodInvoker mi = new MethodInvoker(ShowProcessBar);
this.BeginInvoke(mi);
//Thread.Sleep( 500 );//Sleep a while to show window
bool blnIncreased = false;
object objReturn = null;
do
{
Thread.Sleep( 50 );
objReturn = this.Invoke( this.myIncrease,
new object[] { methodC(i) });//methodC()为关于i的的函数,用来显示进度
blnIncreased = (bool)objReturn ;
}
while( blnIncreased );
}
正如你所说,Invoke调用要等待方法A结束才开始,也就是方法B结束,这样ThreadFun没有执行也就不能实时的反映进度了,请问怎样解决。
谢谢!
zhouxiaoming 发表于2008年3月14日 15:36:33  IP:举报回复删除
我刚刚发了一遍了啊,呵呵。也就如何同步的问题?我把Invoke改成BeginInvoke就有错误发生,真的不知道怎么弄了。
拜谢解答如何同步!!!
csppqiuyx 发表于2008年5月23日 11:59:17  IP:举报回复删除
感谢了,
zhmvb 发表于2008年5月25日 22:09:55  IP:举报回复删除
偶像,能不能把完整的代码给我发一份?zhmvb@tom.com
谢谢了!
gengxin_914 发表于2008年7月7日 21:43:44  IP:举报回复删除
如果想用代码来关闭该等待窗口该怎么实现啊,比如:
Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );
thdSub.Start();
//执行长时间等待的函数,执行时间不确定
//函数执行完毕后自动关闭等待窗口
gengxin_914 发表于2008年7月14日 15:22:27  IP:举报回复删除
如果我想通过代码提前将等待窗口关闭,那用代码该怎么写啊?
zhenqiyi 发表于2008年7月23日 23:00:27  IP:举报回复删除
我是个菜鸟,第一次做DLL,我是做了一个带界面的DLL,ASP.NET里调用它的,就是说在网页里点下按钮打开DLL里的那个页面,在这个页面里有一个打开文件的按钮,按这个就会报错:在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常。
具体线程什么的我不懂啊,代码是这样的,大师有空帮我看看哈:
在ASP.NET里:
protected void OKADD_Click(object sender, EventArgs e)
{//testExcelToOracle是我的DLL的NAMESPACE
testExcelToOracle.Form1 TESTadd = new testExcelToOracle.Form1 ();
TESTadd.ShowDialog ();
}
DLL的FORM1里:
private void openExcel_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "c:\\";//注意这里写路径时要用c:\\而不是c:\
// openFileDialog.Filter="文本文件|*.*|Excel文件|*.xls|C#文件|*.cs|所有文件|*.*";
openFileDialog.Filter = "Excel文件|*.xls";
openFileDialog.RestoreDirectory = true;
openFileDialog.Title = "打开文件";
openFileDialog.FilterIndex = 1;
//在这里报错的。。
if (openFileDialog.ShowDialog() == DialogResult.OK )
。。。。。。
}
万分感谢!!!
zhenqiyi 发表于2008年7月23日 23:00:40  IP:举报回复删除
我是个菜鸟,第一次做DLL,我是做了一个带界面的DLL,ASP.NET里调用它的,就是说在网页里点下按钮打开DLL里的那个页面,在这个页面里有一个打开文件的按钮,按这个就会报错:在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常。
具体线程什么的我不懂啊,代码是这样的,大师有空帮我看看哈:
在ASP.NET里:
protected void OKADD_Click(object sender, EventArgs e)
{//testExcelToOracle是我的DLL的NAMESPACE
testExcelToOracle.Form1 TESTadd = new testExcelToOracle.Form1 ();
TESTadd.ShowDialog ();
}
DLL的FORM1里:
private void openExcel_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "c:\\";//注意这里写路径时要用c:\\而不是c:\
// openFileDialog.Filter="文本文件|*.*|Excel文件|*.xls|C#文件|*.cs|所有文件|*.*";
openFileDialog.Filter = "Excel文件|*.xls";
openFileDialog.RestoreDirectory = true;
openFileDialog.Title = "打开文件";
openFileDialog.FilterIndex = 1;
//在这里报错的。。
if (openFileDialog.ShowDialog() == DialogResult.OK )
。。。。。。
}
万分感谢!!!
No4000 发表于2008年8月19日 11:07:50  IP:举报回复删除
按你所做的, 我复制代码也不成功,为何显示数据后才出现窗体?
godgirl 发表于2009年1月19日 14:55:24  IP:举报回复删除
请问LZ如果我想在弹出的窗体上做一个取消怎么做了?
tiandaiyin 发表于2010年8月24日 12:20:09  IP:124.205.192.*举报回复删除
代码给全好吧,还有就是注释能不能用国语,俺是中国人,希望能看自己国家的语言!
leonardo_ding 发表于2010年9月28日 13:23:28  IP:218.94.124.*举报回复删除
knight94 你好!
leonardo_ding 发表于2010年9月28日 13:24:03  IP:218.94.124.*举报回复删除
向你请教一个问题!
leonardo_ding 发表于2010年9月28日 13:28:31  IP:218.94.124.*举报回复删除
private void cccc_ItemClick(object sender, ItemClickEventArgs e) { thdSub = new Thread(new ThreadStart(ThreadFun)); thdSub.Start(); } private void ThreadFun() { 任务1 任务2 任务3 任务4 } 这四个任务都是用委托写的 this.Invoke()的形式我不知道如何写个进度条,让进度条从逐格推进,直到满格,任务也处理完成???谢谢指导
  • 发表评论
  • 表 情:
  • 顶 砸 棒 大笑 愤怒 大哭 疑问 汗 呕吐 送花
  • 评论内容:
热门招聘职位

公司简介|招贤纳士|广告服务|银行汇款帐号|联系方式|版权声明|法律顾问|问题报告
北京创新乐知广告有限公司 版权所有, 京 ICP 证 070598 号
世纪乐知(北京)网络技术有限公司 提供技术支持
江苏乐知网络技术有限公司 提供商务支持
Email:webmaster@csdn.net
Copyright © 1999-2010, CSDN.NET, All Rights Reserved
GongshangLogo

 

最近看了好多人问这方面的问题,以前我也写过一篇blog,里面说了如何在子线程中控制进度条。但目前大多数环境,需要弹出模式窗口,来显示进度条,那么只需要在原先的基础上稍作修改即可。

首先是进度条窗体,需要在上面添加进度条,然后去掉ControlBox。除此外,还要增加一个方法,用来控制进度条的增加幅度,具体如下:

/// <summary>

/// Increase process bar

/// </summary>

/// <param name="nValue">the value increased</param>

/// <returns></returns>

public bool Increase( int nValue )

{

if( nValue > 0 )

{

if( prcBar.Value + nValue < prcBar.Maximum )

{

prcBar.Value += nValue;

return true;

}

else

{

prcBar.Value = prcBar.Maximum;

this.Close();

return false;

}

}

return false;

}

接着就是主窗体了,如何进行操作了,首先需要定义两个私有成员,一个委托。其中一个私有成员是保存当前进度条窗体对象,另一个是保存委托方法(即增加进度条尺度),具体如下:

private frmProcessBar myProcessBar = null;

private delegate bool IncreaseHandle( int nValue );

private IncreaseHandle myIncrease = null;

接着要在主窗体中提供函数来打开进度条窗体,如下:

/// <summary>

/// Open process bar window

/// </summary>

private void ShowProcessBar()

{

myProcessBar = new frmProcessBar();

// Init increase event

myIncrease = new IncreaseHandle( myProcessBar.Increase );

myProcessBar.ShowDialog();

myProcessBar = null;

}

那么现在就可以开始创建线程来运行,具体如下:

/// <summary>

/// Sub thread function

/// </summary>

private void ThreadFun()

{

MethodInvoker mi = new MethodInvoker( ShowProcessBar );

this.BeginInvoke( mi );

Thread.Sleep( 1000 );//Sleep a while to show window

bool blnIncreased = false;

object objReturn = null;

do

{

Thread.Sleep( 50 );

objReturn = this.Invoke( this.myIncrease,

new object[]{ 2 } );

blnIncreased = (bool)objReturn ;

}

while( blnIncreased );

}

注意以上,在打开进度条窗体和增加进度条进度的时候,一个用的是BeginInvoke,一个是Invoke,这里的区别是BeginInvoke不需要等待方法运行完毕,而Invoke是要等待方法运行完毕。还有一点,此处用返回值来判断进度条是否到头了,如果需要有其他的控制,可以类似前面的方法来进行扩展。

启动线程,可以如下:

Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );

thdSub.Start();

这样,一个用模式打开进度条窗体就做完了。

posted on 2010-11-10 10:59  冰危节奏  阅读(2549)  评论(0)    收藏  举报

导航