好吧,后来才发现有Semaphore和SemaphoreSlim这两个类。

以前的答案:

最近.Net项目中用到了网页截图功能,这个截图功能是类似后台开了一个IE浏览器默默加载某个网页然后截取下来保存,因此并发截图量不能太大,但是又不能一个一个的截(因为截图函数里要设置等待网页加载时间,故一个一个截的话截完N个图要很长时间)。由此引出N个线程一次性只能让_concurrentSnapCount个线程进入截图区域。

一开始我是用一个计数器来计数截图区域进入了多少个线程,达到_concurrentSnapCount就不让进入,而阻塞部分用的是Monitor.Enter(_lkSnap);该部分代码:

/* 注:_lkSnap和_lkNum是object类型全局对象,_snapingCount是int类型初值为0的全局变量代表同时进入截图区域的线程数,_concurrentSnapCount是int类型常量且值大于1*/

          // 一次性只有一个线程能获得Enter返回
          Monitor.Enter(_lkSnap);
lock (_lkNum)
{
++_snapingCount; // 进入截图区域的线程数+1
if (_snapingCount < _concurrentSnapCount) // 判断进入截图区域的线程数是否达到设定最大值
{
Monitor.Exit(_lkSnap); // 没有达到最大值,Exit,让其它线程也能进入截图区域
}
}
// 一次性只能有_concurrentSnapCount个线程调用此函数
var img = WebPageSnapshot.WebSnapshot(postUrl, _snapshotWidth, delayTime); // 这块即为截图区域
lock(_lkNum)
{
// 判断此时在截图区域的线程数,如果等于最大值说明此时调用Enter会被阻塞,而次线程已经截图完毕可以是否截图区域的占用,故Exit。
if(_snapingCount == _concurrentSnapCount)
Monitor.Exit(_lkSnap); // 注意,Exit(obj)只能在Enter(obj)后调用一次,这也是为什么要判断_snapingCount==_concurrentSnapCount的原因
--_snapingCount; // 进入区域的线程数变量-1
}
DoOtherThing(...);

上面的代码是存在bug的,即下面的Monitor.Exit(_lkSnap)有可能产生异常信息:从不同步的代码块中调用了对象的同步方法。

比如说当_concurrentSnapCount值为2时,如果有三个线程要进入截图区域,第一个进入后由于_snapingCount < _concurrentSnapCount 为true故Monitor.Exit(...),

因此第二个线程Enter成功,但是_snapingCount < _concurrentSnapCount为false,故不执行Exit,因此第三个线程会被Enter阻塞。

我们假设第一个进入截图区域的线程也是第一个执行完WebSnapshot(...),该线程在判断_snapingCount == _concurrentSnapCount为true,故会执行Monitor.Exit(_lkSnap),由此

引发 从不同步的代码块中调用了对象的同步方法 的异常,因为最新的Enter(_lkSnap)是第二个线程执行的(或说_lkSnap的锁是由第二个线程加的),而下面的Exit(_lkSnap)却是由第一个线程执行,

释放锁只能由加该锁的线程释放。如果只能由加锁的线程释放那么就变成了必须一次性进入截图区域的_concurrentSnapCount个线程全部执行完,然后由最后进入区域的线程释放锁,再进入下一批。

变成了分批进入而不是出一个进一个,这显然不和要求。

要做到符合要求的功能要将Monitor.Enter(_lkSnap)、Monitor.Exit(_lkSnap)改成由AutoResetEvent对象来实现,具体代码:     

/*_autoRstEvt 也是全局AutoResetEvent对象,且initialState为true*/

       _autoRstEvt.WaitOne();  // 首个线程进入将直接获得信号并自动执行Reset阻塞下一个线程
       lock(_lkNum)
{
++_snapingCount;
if(_snapingCount < _concurrentSnapCount)
{
_autoRstEvt.Set(); // 未满,让下一个正在WaitOne的线程获得信号,或下一个将要WaitOne()的线程在WaitOne时直接获得信号。
}
}
// 一次性只能进入_concurrentSnapCount个
var img = WebPageSnapshot.WebSnapshot(postUrl, _snapshotWidth, delayTime);
lock(_lkNum)
{
// 注意,_autoRstEvt可以重复Set,这点跟Monitor.Exit(obj)不一样,故此处判断其实没必要直接Set()就行。
if(_snapingCount == _concurrentSnapCount)
_autoRstEvt.Set();
--_snapingCount;
}
       DoOtherThing(...);

至此,实现Count个线程并发进入某一区域且某个线程离开后进入新线程的功能完成。

最新文章

  1. {POJ}{3903}{Stock Exchange}{nlogn 最长上升子序列}
  2. quartz.net 使用(一)-执行定时计划任务
  3. [Latex]实现行内高亮
  4. poj1961 Period
  5. Python开发【第一篇】Python基础之字符串格式化
  6. java使用正则表达式验证IP V4、 IP V6
  7. CSS背景特殊属性值
  8. Web程序工作原理
  9. 经常使用的DB2命令(2)
  10. Linux下精确控制时间的函数
  11. js--事件对象的理解4
  12. 微信小程序问题2:未配置
  13. 游戏UI框架设计(三) : 窗体的层级管理
  14. 201521123104《Java程序设计》第11周学习总结
  15. MSSQL 常用操作
  16. 深度揭秘腾讯云TSF日调用量超万亿次背后技术架构
  17. 记一次查询超时的解决方案The timeout period elapsed......
  18. vbs 去掉字符串中的空格
  19. VNC 分辨率修改
  20. Maximum Width Ramp LT962

热门文章

  1. Mac OS X更新VirtualBox以后Genymotion无法启动的一种情况
  2. jq获取被选中的option的值。jq获取被选中的单选按钮radio的值。
  3. C++ 指针和引用 吐血整理 Pointer&amp;Reference
  4. 实用的jQuery技巧
  5. PHP面向对象-----魔术方法
  6. [置顶] Java WebService接口生成和调用 图文详解
  7. javascript设计模式——中介者模式
  8. IEEE Trans 2008 Gradient Pursuits论文学习
  9. dubbo源码—SPI
  10. HDOJ 4251 The Famous ICPC Team Again