Spiga

使用 .NET 实现 Ajax 长连接 (Part 2 - Mutex Wait & Signal)

2008-03-08 17:21 by Cat Chen, 7318 visits, 收藏, 编辑

在上一次的文章中,我们说到了如何设计一个ASP.NET Web Service来处理长连接请求。很多人对此就提出了问题,如何hold住请求让它30秒不断开了?这其实很简单,只需要Sleep()一下就可以了:

Thread.Sleep(30 * 1000);

然而问题是,我们不是要等30秒然后看看是否有事件需要返回,而是在这30秒内随时有事件随时返回。因此,我们需要一套机制来在等待的过程中检查是否有事件发生了。

Monitor模型

在.NET里面,大家最熟悉的线程同步模型应该就是Monitor模型了。没听说过?就是C#的那个lock关键字,实际上它编译出来就是一对Monitor.Enter()和Monitor.Exit()。

通过lock命令,我们可以针对一个对象创建一个临界区,代码执行到临界区入口时必须获取到该对象的锁才能执行下去,并且在临界区的出口释放该锁。然而这种模型不太适用于解决我们的问题,因为我们需要等待一个事件,如果使用lock来等待的话,那就是说要先在Web Service外部把对象锁上,然后等事件触发了就解锁,这时候Web Service才顺利进入临界区域。

事实上,要进行这类型的阻塞,还有一个更好的选择,那就是Mutex。

Mutex模型

Mutex,也就是mutual exclusive的缩写,“互斥”的意思。Mutex是如何运作的?这有点像是银行的排队叫号系统,所有等待服务的人都坐在大厅里等候(wait)被叫,当一个服务窗口空闲时它就会发出一个信号(signal)来通知下一位等候服务的人。总之,所有执行wait指令的线程都在等候,而每一个signal能够让一个线程结束等候继续执行。

在.NET里面,wait和signal这两个操作分别对应Mutex.WaitOne()和Mutex.ReleaseMutex()这两个方法。我们可以让Web Service的线程使用Mutex.WaitOne()进入等候状态,而在事件发生时使用Mutex.ReleaseMutex()来通知Web Service线程。因为必须在Mutex.ReleaseMutex()发生后Mutex.WaitOne()才可能继续执行下去,因此能够执行下去就证明必然有事件发生了并且调用了Mutex.ReleaseMutext(),这时候就可以放心地去读取事件消息了。

简单示例

在选定使用Mutex模型后,我们来编写一个简单的示例。首先,我们要在WebService派生类内定义一个Mutex,还有一个代表消息的字符串。

Mutex mutex = new Mutex();
string message;

然后,我们定义两个WebMethod。为了把问题简单化,我们选用上一篇文章中开头所说的两个函数签名,也就说只能在一个Web Service内自己发自己收,没有发送目标的概念,也没有超时的概念,还没有可靠性设计。同时,我们将Message类型替换为普通字符串,以便于我们测试。

我们先编写发送消息的函数:

public void Send(string message) {
  this.message = message;
  this.mutex.ReleaseMutex();
}

在这个发送函数里,首先我们把消息放进了类内全局的变量中,然后让全局的Mutex类释放一个signal。这时候,如果有线程在等待,它可以马上执行下去。如果此时没有线程在等待,那么下一个wait的线程执行到该阻塞的地方就能够不受阻塞继续执行下去。

现在我们来编写接收消息的函数:

public string Wait() {
  this.mutex.WaitOne();
  return this.message;
}

接收函数一开始就进入wait状态。在得到signal后,需要做的事情就是把全局的消息返回给客户端。

亲身体验

最后,我们可以通过ASP.NET Web Service本身支持的Web测试界面来测试一下我们的代码。我们开两个浏览器窗口,一个进入Send()调用,一个进入Wait()调用。然后我们按照如下方法来测试:

  1. 首先执行Send("Hello"),然后执行Wait()。这时候你可以马上看到"Hello"。
  2. 首先执行Wait(),让它等待返回,这时候执行Send("Hello")。随后你可以看到Wait()那段返回"Hello"了。
  3. 按如下顺序执行:Send("Hello");Wait();Send("World");Wait();
  4. 按如下顺序执行:Send("Hello");Send("World");Wait();Wait();
  5. 按如下顺序执行:Wait();Wait();Send("Hello");Send("World");
  6. 按如下顺序执行:Wait();Send("Hello");Wait();Send("World");

你会发现这样一些奇怪的结果:第3个测试返回的是"World"和"World"。第5个测试先返回"Hello"的并不一定是先执行的那个Wait()线程。后者在某些情况下不是什么问题,特别是长连接中一般之后一个Wait()线程在等待中,所以我们可以不管。而前者,则是因为没有消息队列所造成的,我们只有长度为1的消息窗口,所以只能缓存最后一个消息。这个问题我们将在下一篇文章中解决。

小结

在本文中,我们看到了不同的线程同步模型的差异。Monitor模型的lock本质上是一个Semaphore,也就是一个不能连续signal的Mutex,一个signal发出去后必须被一个wait接收了才能进行下一次的signal。同时,Semaphore也限制了signal和wait必须在同一个线程内成对执行,而Mutex则没有此限制。虽然.NET是针对Monitor模型优化的,但在我们的需求当中,只能通过Mutex模型来解决。

接着,我们便写了一个小小的消协发送与接收函数,实现了我们想要的阻塞式Web Service。同时我们也看到了没有消息队列造成的问题,因此确定接下来我们要做一个消息队列。如果你想知道消息队列如何编写,欢迎订阅我的blog:

Add your comment

22 条回复

  1. #1楼 钢钢      2008-03-09 10:10
    你所说的 Monitor模型和 Mutex模型,我在学校的操作系统课程中都专门的针对此讨论过,不过讨论的不是针对这些模型,而就是同步与互斥问题。

    看来大学还没有白读^_^,也谢谢这篇文章
     回复 引用 查看   
  2. #2楼[楼主] Cat Chen      2008-03-09 13:42
    @钢钢
    是的是的,不过我对这些知识都忘记得差不多了……

    对于.NET来说,MSDN写道Monitor的优化比Mutex要好,到底怎样我还不清楚。
     回复 引用 查看   
  3. #3楼 micYng      2008-03-10 10:08
    Mutex消耗资源比较厉害,因为是跨边界的
     回复 引用 查看   
  4. #4楼 obk[未注册用户]2008-03-16 21:53
    希望能快一点有Part3啊..很期待能用.net实现长连接的说..
     回复 引用   
  5. #5楼 Must Studying[未注册用户]2008-03-28 10:56
    以前不懂线程方面的 东西,现在懂一点了
     回复 引用   
  6. #6楼 阿不      2008-04-11 11:24
    @Cat Chen
    你的这篇文章给了我很大的启发。
    长连接请求的方式,确实我们应该把优化的重点放在服务器端的设计上。如果服务器使用轮循的方式一样也是会造成很多资源浪费。而我们如果使用资源互斥的做法,可以让请求数据一端等待通知即可,而不必定时去查询。大大提高性能的同时,又可以大大提高实时性。
     回复 引用 查看   
  7. #7楼 crouse[未注册用户]2008-04-25 13:40
    我用这个小例子测试,wait挡不到数据。跟踪时message为null.
     回复 引用   
  8. #8楼[楼主] Cat Chen      2008-04-27 12:45
    @crouse
    Wait()的同时,你用Send()把消息发过去了吗?
     回复 引用 查看   
  9. #9楼 crouse[未注册用户]2008-04-28 14:23
    不是先send一个消息,然后wait吗?
     回复 引用   
  10. #10楼 faa[未注册用户]2008-09-25 16:08
    没有完整实例吗?
     回复 引用   
  11. #11楼 comet.net[未注册用户]2008-10-22 18:06
    楼主接着写啊,期待后续
     回复 引用   
  12. #12楼 lee576[未注册用户]2008-11-06 14:20
    期待有完整的demo代码,整套实现
     回复 引用   
  13. #13楼 王国金      2008-12-02 14:54
    是的。希望楼主分享一个DEMO。期待一下!
     回复 引用 查看   
  14. #14楼 Andy Huang      2009-01-21 17:02
    期待这篇文章的后续...
    同时希望lz 能提供更多的连接文章,代码.
     回复 引用 查看   
  15. #15楼 小懒人      2009-04-01 10:08
    和 AutoResetEvent 相比呢?
     回复 引用 查看   
  16. #16楼 wwwfind      2009-12-30 18:57
    楼主是否可以把part3写完呢,我百思不得其解,利用mutex可以如何很好的解决长连接控制的问题。
    使用thread.sleep()配合Interrupt()是不是也可以呢?谢谢!
     回复 引用 查看   
  17. #17楼 gotolnc      2010-01-24 03:14
    这个系列仍未成啊,老大
     回复 引用 查看   
  18. #18楼 lirq      2010-09-18 23:57
    啊啊啊
    掉坑里了..
     回复 引用 查看   
  19. #19楼 大张DOTNET      2011-05-15 15:07
    第三部分怎么没有写呢???还会写吗?
     回复 引用 查看   
  20. #20楼[楼主] Cat Chen      2011-05-15 21:23
    @大张DOTNET
    看来不会了⋯⋯因为没有时间了。
     回复 引用 查看