posts - 257, comments - 1336, trackbacks - 63, articles - 8
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

初探.NET多线程

Posted on 2005-09-09 18:15 FantasySoft 阅读(2736) 评论(12)  编辑 收藏 所属分类: All About SoftLovely DotNet
         好多天没有更新Blog了,一直都在忙着寻找如何将IronPython集成至GUI中的方法。一开始我把问题看得太简单了,因为我看了来自CodeProject的ShellControl - A console emulation control这篇文章之后,很幼稚地认为只需要把源代码中的cmd.exe换IronPythonConsole.exe 就可以大功告成了,然而事实证明我的想法是很愚蠢的。尽管IronPythonConsole与cmd同为可执行文件,但是它们与作为输入输出的RichTextBox之间的关系是不同的。对cmd而言,是一种顺序的处理方式:TextBox接受用户输入-> 用户按下Enter触发相应的EventHandler,在EventHandler中创建Process以运行cmd.exe -> 将结果返回至TextBox。在这个过程中,每一条指令的输入都会相应地创建新的Process,而这个Process的life cycle仅仅存在于用户按下Enter触发的事件响应函数中;反观IronPythonConsole的情况就不是这样的了,我们需要的是一个GUI Thread来响应用户的输入,同时需要一个Worker Thread调用IronPythonConsole来完成用户输入并给出相应的输出,很明显我需要进行多线程的开发了。面对着多线程,我真的很困窘,因为自己在这个方面的经验几乎为零。不过没有办法,为了FantasyPython,只能明知山有虎,偏向虎山行了。
        也许是自己天性愚钝吧,看了两天的文章,也还没有很好的solution,不过对于Multithreading倒是有了一点小小的认识,谨记于此。首先是使用多线程的重要性了。这点其实不必多言,大家都会知道把占用大量CPU周期的数据处理放到GUI Thread中,造成GUI无法响应用户的操作是一种十分糟糕的做法。为了实现更好的用户体验,就应该创建新的进程来处理持续时间较长的操作,通常我们会把负责非UI操作的进程称为Worker Thread(工作进程)。重要性说完了,让我们关注到更具体的多线程实现上来。
        在.NET中进行多线程开发,会有很多选择,其中最常见的就是使用Control.Invoke方法了。Invoke方法提供了一种在Worker Thread调用GUI Thread中delegate的途径。同时结合Control.InvokeRequired的属性,我们也可以轻易地判断某个线程是否为创建该Control的线程。为了对Invoke方法有更深入的认识,我仔细地阅读了CodeProject上《Worker Threads In C#》一文[1]。在文中有这样的代码:
m_form.Invoke(m_form.m_DelegateAddString, new Object[] {s});

这里给出了Invoke方法的一种使用方式,但是可惜的是,我并不能对其使用的必要性有充分的理解。因为我把这段代码改为m_form.AddString(s),并且将AddString方法的access modifier由private改为public,程序仍然是运行好好的。那么Invoke方法的意义在于什么呢?而MSDN给Invoke方法下的定义又是如此的简单:Executes the specified delegate on the thread that owns the control's underlying window handle. 真的是看不明白了,烦请各位朋友不吝赐教。
        问题虽然有,但是认识还得接着说。多线程开发很重要的一点就是线程间的通信问题,在CodeProject的这篇文章中,Worker Thread与GUI Thread是通过ManualResetEvent来实现的。我们从文中的代码:
// Worker Thread
if ( m_EventStop.WaitOne(0true) ) {
    m_EventStopped.Set();
    
return
;
}


// GUI Thread
if ( m_WorkerThread != null  &&  m_WorkerThread.IsAlive ) {
    m_EventStopThread.Set();
    
while (m_WorkerThread.IsAlive) 
{
        
if ( WaitHandle.WaitAll((new ManualResetEvent[] {m_EventThreadStopped}), 100,true) ) 
{
            
break
;
        }


        Application.DoEvents();
    }

}

  
可以看到Worker Thread与GUI Thread之间的通信过程:
        1、当Worker Thread处于活动状态,而GUI Thread通过设置m_EventStopThread的状态向Worker Thread发出Signal,并使自己进入循环等待状态,直至接收到由Worker Thread发出的Signal;
        2、Worker Thread在接收到该Signal之后采取相应的操作,例子中的操作就是通过设置m_EventStopped的状态向GUI Thread发出自己已经停止的Signal并且返回;
        3、GUI Thread接收到来自Worker Thread发出的Signal后,跳出循环;
        呼,终于说完了。由于自己刚刚开始接触C#的MultiThreading开发,疏漏一定不少。请各位路过的朋友不要错过扔砖头的机会哦,^_^

        [1] 小新0574为这篇不错的文章提供了译文,有兴趣的朋友可以参考一下中文版。

Feedback

#1楼    回复  引用  查看    

2005-09-09 21:36 by idior      
希望介绍一下有关线程安全方面的知识. 我对此一直没有深入研究过,似乎资料也不多,尤其是在.net下.

#2楼    回复  引用  查看    

2005-09-09 21:36 by Cavingdeep      
Yo,

"Executes the specified delegate on the thread that owns the control's underlying window handle."

这句是说这个delegate将会在UI线程上执行,这样才可以正确控制UI。所谓UI线程,就是那个拥有被操作控件的window handle的线程。:)

#3楼 [楼主]   回复  引用  查看    

2005-09-10 01:55 by FantasySoft      
To Cavingdeep:MSDN的解释我能看懂,但是我直接调用m_form的方法跟使用Invoke有什么区别呢?这才是我真正想关注的。

To idior: 多线程肯定是我接下来要关注的话题,因为做FantasyPython离不开Multithreading。

#4楼    回复  引用    

2005-09-10 03:36 by unnamed [未注册用户]
直接调就会令那个方法的执行环境(context)为work thread,显然有潜在的资源竞争危险。例子中AddString,由于只有work一个thread调用,因此没什么问题

根据Invoke的字面解释,估计会是通过post message的方式让control的owner线程调用

#5楼    回复  引用  查看    

2005-09-10 08:52 by Cavingdeep      
@FantasySoft
不是都说了嘛,对UI的操作只能在UI线程起作用,所以必须在UI线程调用。这就是为什么一定要用Invoke或BeginInvoke。你可以多写两个操作UI的例子来验证一下。

当然如果是与UI无关的逻辑那么也不必在UI线程调用。这取决于你的那个AddString方法到底做了什么。

#6楼 [楼主]   回复  引用  查看    

2005-09-10 10:22 by FantasySoft      

To Cavingdeep: 我似乎有点开窍了,不过只是有点。咔咔~~~

你说的“对UI的操作只能在UI线程起作用,所以必须在UI线程调用”,这种必要性是需要自己在编程中自觉地遵循呢?还是说存在某些mechanism使得这种必要性成立呢? 我对这个感觉还是很模糊,得再找些资料来参考一下。

我倒觉得unnamed的解释挺有说服力,园子里的Rustle Liu的一篇文章Invoke 和 BeginInvoke 的真正涵义,也提到了这一点。

#7楼    回复  引用  查看    

2005-09-10 12:55 by Cavingdeep      
@FantasySoft

看来你进入了多线程与UI的混乱啊。:) 这部分实际上你不需要考虑多线程,只要你理解所有操作UI的代码不管写在哪里,都必须在UI线程上执行才能起作用,这是Windows的要求。

单线程时因为所有代码其实都在UI线程上执行所以没有任何问题,多线程时如果想在一个worker thread中操作UI的话就必须用Control.Invoke将这段代码的执行留在UI线程中,而不是你目前的这个worker thread中。如果你调用Control.Invoke那么你的worker thread将等待UI线程处理这段代码,如果是Control.BeginInvoke,也就是异步的话,你的worker thread就不会等待UI线程处理,而是立刻返回,你的代码执行会被Queue住。就是这个样子,其实很简单,你把事情想的复杂了!这里和资源争夺没有关系。

Rustle Liu在他的文章里也说的很明白了,不过题目有点不对,Control的Invoke与BeginInvoke与异步或delegate的Invoke与BeginInvoke是要区分开的。

#8楼 [楼主]   回复  引用  查看    

2005-09-12 01:20 by FantasySoft      
To Cavingdeep: 那么我直接进行方法调用会有什么问题出现吗?

#9楼    回复  引用    

2005-09-12 03:13 by unnamed [未注册用户]
从理论角度分析,多线程安全只有一个需要解决的问题,就是临界资源的使用,由此衍生出当采用同步技术时可能发生的死锁,无论什么操作系统皆是如此

在win32环境中,ms建议“在一个线程环境中操作gui环境对象”,就是因为基于效率,这些操作中使用的数据大多没有使用同步来保护。知道了需要遵守这个原则的理由就好办了,一方面,对于dc等非消息驱动而直接操作的gdi对象来说,这个原则必须被遵守(或者由用户代码负责同步),否则,由于可能的资源竞争将导致不可测的后果;另一方面,对于window(包括各common control)对象,由于大多数操作采用消息驱动,即由window proc根据消息负责完成任务,用户代码一般都采用send message(或post或其他wrapper api)激活window proc而不是直接拿proc的地址去调用,系统能保证这些方式激活的window proc都在它的owner线程环境中执行

注:SendMessage的remark,摘自msdn:
If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message.

由此可知,win32 gui环境,在同一线程操作gui对象这是一个指导原则,如果用户对系统很熟悉,可以在适当情况下违背这个原则

就目前来看,.net底层仅仅是对win32api的包装(也包括其机制),因此上面的结论也适用,除非.net另外作出硬性规定

如此一来,所有问题都可以通过文自己另一个问题而得到解答,比如“那么我直接进行方法调用会有什么问题出现吗?”可以转换为“这个方法中会使用到临界资源吗(也包括这个方法间接使用的方法,当然也包括.net或win32的api)?”

btw:work thread和gui thread并没有本质区别,不过这个概念并非对系统透明,系统为gui thread维护更多的数据,基于效率,在需要使用时才申请空间,系统认为没有调用任何gui相关api的线程就是work thread,只要它调用了任何gui相关api就自动转为gui thread(无法反向转换)。实际应用中,有的work thread会有实现一个消息循环等待其他线程发来的命令,这时候逻辑上它是work thread,由于它使用了消息队列相关api(属于gui api),那么它也会被操作系统标为gui thread

#10楼    回复  引用    

2005-09-16 15:44 by 王 [未注册用户]
你可以参看一下:

《How to embed IronPython script support in your existing app in 10 easy steps》

http://blogs.msdn.com/jmstall/archive/2005/09/01/Howto_embed_ironpython.aspx

#11楼    回复  引用    

2005-09-16 15:46 by 王 [未注册用户]
有趣的是,这个blog最后一个回复的人也在做一个IDE,名叫FantasyPython,不知是不是你的朋友。
》So I try to accomplish one mission impossible, developping one IDE that supports IronPython. And the IDE is named FantasyPython.

地址见上一个回复。

#12楼 [楼主]   回复  引用  查看    

2005-09-16 15:58 by FantasySoft      
To 王: 谢谢您的information。 那篇blog我已经看过了,而且最后回复的人就是我。呵呵~~

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-09-12 13:49 编辑过


相关链接:

历史上的今天:
2004-09-09 一发不可收拾的学习
2004-09-09 无以规矩,不成方圆