delegate 与 多线程(摘自网络)

本文探讨了在.NET中如何实现多线程与UI的安全交互,介绍了如何使用委托(delegate)来避免直接从非UI线程操作控件,以及如何在多线程环境下正确地传递参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

delegate 与 多线程

http://www.putfly.com/show.aspx?id=252&cid=11
很多时候写 windows 程序都需要结合多线程,在 .net 中用如下得代码来创建并启动一个新的线程。
None.gif public  void  ThreadProc();
None.gif
None.gifThread thread  =  new  Thread(  new  ThreadStart( ThreadProc ) );
None.gif
None.gifthread.IsBackground  =  true ;
None.gif
None.gifthread.Start();

但是很多时候,在新的线程中,我们需要与UI进行交互,在.net中不允许我们直接这样做。可以参考MSDN中的描述:

“Windows 窗体”使用单线程单元 (STA) 模型,因为“Windows 窗体”基于本机 Win32 窗口,而 Win32 窗口从本质上而言是单元线程。STA 模型意味着可以在任何线程上创建窗口,但窗口一旦创建后就不能切换线程,并且对它的所有函数调用都必须在其创建线程上发生。除了 Windows 窗体之外,.NET Framework 中的类使用自由线程模型。

STA 模型要求需从控件的非创建线程调用的控件上的任何方法必须被封送到(在其上执行)该控件的创建线程。基类 Control 为此目的提供了若干方法(Invoke、BeginInvoke 和 EndInvoke)。Invoke 生成同步方法调用;BeginInvoke 生成异步方法调用。

Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。

正如所看到的,我们必须调用Invoke方法,而BeginInvoke可以认为是Invoke的异步版本。调用方法如下:

None.gif public  delegate  void  OutDelegate( string  text);
None.gif
None.gif public  void  OutText( string  text)
None.gif
ExpandedBlockStart.gifContractedBlock.gif dot.gif {
InBlock.gif
InBlock.gif     txt.AppendText(text);
InBlock.gif
InBlock.gif     txt.AppendText( " " );
InBlock.gif
ExpandedBlockEnd.gif}

None.gif
None.gifOutDelegate outdelegate  =  new  OutDelegate( OutText );
None.gif
ExpandedBlockStart.gifContractedBlock.gif this .BeginInvoke(outdelegate,  new  object [] dot.gif {text} );

如果我们需要在另外一个线程里面对UI进行操作,我们需要一个类似OutText的函数,还需要一个该函数的委托delegate,当然,这里展示的是自定义的,.net中还有很多其他类型的委托,可以直接使用,不需要而外声明。例如:MethodInvoker和EventHandler,这两种类型委托的函数外观是固定的,MethodInvoker是void Function()类型的委托,而EventHandler是void Function(object, EventArgs)类型的委托,第一个不支持参数,第二中的参数类型和数量都是固定的,这两种委托可以很方便的调用,但是缺乏灵活性。请注意BeginInvoke前面的对象是this,也就是主线程。现在再介绍Control.InvokeRequired,Control是所有控件的基类,对于这个属性MSDN的描述是:

获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。

该属性可用于确定是否必须调用 Invoke 方法,当不知道什么线程拥有控件时这很有用。

也就是说通过判断InvokeRequired可以知道是否需要用委托来调用当前控件的一些方法,如此可以把OutText函数修改一下:

None.gif public  delegate  void  OutDelegate( string  text);
None.gif
None.gif public  void  OutText( string  text)
None.gif
ExpandedBlockStart.gifContractedBlock.gif dot.gif {
InBlock.gif
InBlock.gif     if( txt.InvokeRequired )
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif     dot.gif{
InBlock.gif
InBlock.gif         OutDelegate outdelegate = new OutDelegate( OutText );
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif         this.BeginInvoke(outdelegate, new object[]dot.gif{text});
InBlock.gif
InBlock.gif         return;
InBlock.gif
ExpandedSubBlockEnd.gif     }

InBlock.gif
InBlock.gif     txt.AppendText(text);
InBlock.gif
InBlock.gif     txt.AppendText( " " );
InBlock.gif
ExpandedBlockEnd.gif}

注意,这里的函数没有返回,如果有返回,需要调用Invoke或者EndInvoke来获得返回的结果,不要因为包装而丢失了返回值。如果调用没有完成,Invoke和EndInvoke都将会引起阻塞。

现在如果我有一个线程函数如下:

None.gif public  void  ThreadProc()
None.gif
ExpandedBlockStart.gifContractedBlock.gif dot.gif {
InBlock.gif
InBlock.gif     for(int i = 0; i < 5; i++)
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif     dot.gif{
InBlock.gif
InBlock.gif         OutText( i.ToString() );
InBlock.gif
InBlock.gif         Thread.Sleep(1000);
InBlock.gif
ExpandedSubBlockEnd.gif     }

InBlock.gif
ExpandedBlockEnd.gif}

如果循环的次数很大,或者漏了Thread.Sleep(1000);,那么你的UI肯定会停止响应,想知道原因吗?看看BeginInvoke前面的对象,没错,就是this,也就是主线程,当你的主线程不停的调用OutText的时候,UI当然会停止响应。

 

与以前VC中创建一个新的线程需要调用AfxBeginThread函数,该函数中第一个参数就是线程函数的地址,而第二个参数是一个类型为LPVOID的指针类型,这个参数将传递给线程函数。现在我们没有办法再使用这种方法来传递参数了。我们需要将传递给线程的参数和线程函数包装成一个单独的类,然后在这个类的构造函数中初始化该线程所需的参数,然后再将该实例的线程函数传递给Thread类的构造函数。代码大致如下:

None.gif public  class  ProcClass
None.gif
ExpandedBlockStart.gifContractedBlock.gif dot.gif {
InBlock.gif
InBlock.gif     private string procParameter = "";
InBlock.gif
InBlock.gif     public ProcClass(string parameter)
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif     dot.gif{
InBlock.gif
InBlock.gif         procParameter = parameter;
InBlock.gif
ExpandedSubBlockEnd.gif     }

InBlock.gif
InBlock.gif     public void ThreadProc()
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif     dot.gif{
InBlock.gif
ExpandedSubBlockEnd.gif     }

InBlock.gif
ExpandedBlockEnd.gif}

None.gif
None.gifProcClass threadProc  =  new  ProcClass( " use thread class " );
None.gif
None.gifThread thread  =  new  Thread(  new  ThreadStart( threadProc.ThreadProc ) );
None.gif
None.gifthread.IsBackground  =  true ;
None.gif
None.gifthread.Start();


 

就是这样,需要建立一个中间类来传递线程所需的参数。

那么如果我的线程又需要参数,又需要和UI进行交互的时候该怎么办呢?可以修改一下代码:

None.gif public  class  ProcClass
None.gif
ExpandedBlockStart.gifContractedBlock.gif dot.gif {
InBlock.gif
InBlock.gif     private string procParameter = "";
InBlock.gif
InBlock.gif     private Form1.OutDelegate delg = null;
InBlock.gif
InBlock.gif     public ProcClass(string parameter, Form1.OutDelegate delg)
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif     dot.gif{
InBlock.gif
InBlock.gif         procParameter = parameter;
InBlock.gif
InBlock.gif         this.delg = delg;
InBlock.gif
ExpandedSubBlockEnd.gif     }

InBlock.gif
InBlock.gif     public void ThreadProc()
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif     dot.gif{
InBlock.gif
InBlock.gif         delg.BeginInvoke("use ProcClass.ThreadProc()", null, null);
InBlock.gif
ExpandedSubBlockEnd.gif     }

InBlock.gif
ExpandedBlockEnd.gif}

None.gif
None.gifProcClass threadProc  =  new  ProcClass( " use thread class " ,  new  OutDelegate(OutText));
None.gif
None.gifThread thread  =  new  Thread(  new  ThreadStart( threadProc.ThreadProc ) );
None.gif
None.gifthread.IsBackground  =  true ;
None.gif
None.gifthread.Start();

这里只是我的一些理解,如果有什么错误或者不当的地方,欢迎指出。

转载于:https://www.cnblogs.com/wzyexf/archive/2007/03/25/687298.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值