程序中在运行时,多线程往往会带来许多意想不到的情况,比如下面一个关于消息收发的例子可以说明。
代码段一:
msg.Return = this.SendSysMsg(new CMsg(MTAppPatientMgrPlugIn.QueryPatients, strSqlWhere, strSqlOrder));
if(null == msg.Return)
{
return;
}
this.m_hsOperHandle.Add(msg.Return, msg.Return);
这段代码的功能就是发送一个查询病人的消息,并接受一个返回值(操作句柄)加入到哈希表中。
监听此消息的服务程序在执行完查询操作后会发回一个反馈消息,下面的代码就是返回消息处理:
代码段二:
if(this.m_hsOperHandle.Contains(msg.LParam))
{
this.PostSysMsg(new CMsg(MTFetusWardPlugIn.QueryPregnantWomansResult_InPlugIn, msg));
this.m_hsOperHandle.Remove(msg.LParam);
}
此段代码的功能就是判断哈希表中是否有发送消息所返回的句柄对象,如果有就说明此反馈消息是当前类中所发消息的反馈。此时就作消息的处理,处理完后移除该句柄对象。
这两段代码看起来没有什么不对,发送消息后接受消息反馈处理,并以唯一的句柄对象来区别消息对。但运行起来后结果并不是这样,单步时发现消息已正常发出去了,反馈消息也接收了,但在消息接收处理即代码段二中,在哈希表中并没有找到该消息对的句柄对象。
为什么消息正常发出了句柄却没有加到集合(哈希表)中呢,不可能啊,this.m_hsOperHandle.Add(msg.Return, msg.Return);这一步也有执行过。那唯一的可能就是在接受反馈消息并判断集合中是否有句柄的处理,是在往集合中添加句柄处理之前执行的。那这样就可能了,因为本次操作不止一个线程在处理,问题肯定是出在多线程处理上。
接下来修改程序,将这两段代码的操作资源锁定同步起来,运行起来时果然解决此问题了,改动如下。
代码段一:
lock(m_hsOperHandle.SyncRoot)
{
msg.Return = this.SendSysMsg(new CMsg(MTAppPatientMgrPlugIn.QueryPatients, strSqlWhere, strSqlOrder));
if(null == msg.Return)
{
return;
}
this.m_hsOperHandle.Add(msg.Return, msg.Return);
}
将发送消息返回句柄对象,和判断是否为null并加入集合这几步操作锁定。
代码段二:
lock(m_hsOperHandle.SyncRoot)
{
if(this.m_hsOperHandle.Contains(msg.LParam))
{
this.PostSysMsg(new CMsg(MTFetusWardPlugIn.QueryPregnantWomansResult_InPlugIn, msg));
this.m_hsOperHandle.Remove(msg.LParam);
}
}
同理,以锁定要访问资源m_hsOperHandle的同步对象,来实现发送消息与接受消息同步。
发生此问题的根本原因就是,在线程1发送消息并返回操作句柄对象后,并不能保证就马上将句柄加入集合,因为此时另一个线程2在接受到此消息后也在进行处理,这时可能这个线程先处理完,并发送反馈消息。此时线程1接受到此反馈消息并开始处理,但线程1中发送消息所返回的句柄还没有加入到集合中,所以反馈消息处理时集合中找不到该句柄,而最终导致操作异常。
代码lock(m_hsOperHandle.SyncRoot)保证代码段一的原子性,即保证此块的代码能全部操作完。即使还没有操作完时,代码段二的代码被调用,但这两处的操作已锁定相同的资源m_hsOperHandle,在代码段一还没有处理完,即还没有解锁前,调用代码段二的线程就会被挂起,至到代码段一处理完并解锁后才会执行,那么此时集合中铁定有该消息的句柄对象了。所以此锁定资源的方式,保证了程序执行的正常顺利。