好吧,后来才发现有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个线程并发进入某一区域且某个线程离开后进入新线程的功能完成。

同时只允许Count个线程访问同一块区域的实现方式的更多相关文章

  1. Winform之跨线程访问控件(在进度条上显示字体)

    此文章对于遇到必须使用线程但是没有办法在线程内操作控件的问题的处理  有很好的解决方案(个人认为的.有更好的方案欢迎交流.) 在做跨线程访问之前我们先了解下我们所做的需要达到的效果: 这个是批量的将x ...

  2. [Winform]线程间操作无效,从不是创建控件的线程访问它的几个解决方案,async和await?

    目录 概述 取消跨线程检查 使用委托异步调用 sync和await 总结 概述 最近在qq群里有一朋友,问起在winform中怎么通过开启线程的方式去处理耗时的操作,比如,查看某个目录下所有的文件,或 ...

  3. (委托事件处理)关于多线程执行显示进度条的实例(转)&&线程间操作无效: 从不是创建控件“rtxtEntryNO”的线程访问它。

    关于多线程执行显示进度条的实例! 之前回答了一篇关于怎么在线程中操作进度条的帖子,估计有人看的不是很明白今天没事,写了一个小小的实例,很简单,就2个文件权当抛砖引玉,希望有更好解决方案的人发表一下意见 ...

  4. InvokeHelper,让跨线程访问/修改主界面控件不再麻烦(转)

    http://bbs.csdn.net/topics/390162519 事实上,本文内容很简单且浅显,所以取消前戏,直接开始.. 源代码:在本文最后 这里是一张动画,演示在多线程(无限循环+Thre ...

  5. C#线程 访问资源同步简介

    在多线程应用(一个或多个处理器)的计算中会使用到同步这个词.实际上,这些应用程序的特点就是它们拥有多个执行单元,而这些单元在访问资源的时候可能会发生冲突.线程间会共享同步对象,而同步对象的目的在于能够 ...

  6. Winform中子线程访问界面控件时被阻塞解决方案

    public partial class WebData_Import : Form { //声明用于访问主界面的委托类型 public delegate void deleGetOrderdata( ...

  7. 线程间操作无效: 从不是创建控件“button1”的线程访问它。

    .net2后是不能跨线程访问控件的.,窗体上的控件是当前线程创建的,当用户异步执行一个方法:在该方法中给窗体上的控件赋值,记住:当执行一个异步委托的时候,其实 就是开了一个线程去执行那个方法,这样就会 ...

  8. 【转】线程间操作无效: 从不是创建控件“textBox2” 的线程访问它。

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  9. wpf(怎么跨线程访问wpf控件)

    在编写代码时,我们经常会碰到一些子线程中处理完的信息,需要通知另一个线程(我这边处理完了,该你了). 但是当我们通知WPF的UI线程时需要用到Dispatcher. 首先我们需要想好在UI控件上需要显 ...

随机推荐

  1. 状态机编程思想(2):删除代码注释(目前支持C/C++和Java)

    有时为了信息保密或是单纯阅读代码,我们常常需要删除注释. 之前考虑过正则表达式,但是感觉实现起来相当麻烦.而状态机可以把多种情况归为一类状态再行分解,大大简化问题.本文就是基于状态机实现的. 删除C/ ...

  2. Qt颜色下拉框

    上周为了用Qt写一个类似颜色下拉框的东西,查阅了网上的多数相关资料,依然没有我想要的.终于在周四的时候下定决心重写QCombobox类来实现功能,现在把它贴出来,望看到的人,批评指正.废话不多说,先上 ...

  3. memcache的使用、版本使用和相关配置

    首先准备memcached和php_memcache.dll文件.下载网址:链接:http://pan.baidu.com/s/1c1WODji 密码:yzor 将下载好的memcached.exe放 ...

  4. LinuxRPM包安装

    转载注明出处:原文地址 ## LinuxRPM包安装 二进制包(RPM包.系统默认包) RPM安装 rpm -ivh 包全名(查询依赖网址:http://www.rpmfind.net) -i(ins ...

  5. PHP基础 windows环境下安装Apache Mysql PHP

    本篇文章主要是讲一下我自己安装wamp环境的一些步骤和见解,前方多图预警,慎入!!!!! PHP运行环境  : Linux下的三种安装方式:源码包安装.rpm包安装.集成环境安装(lnmp) wind ...

  6. 网页设计——5.table布局

    今天做一个大的页面,主要是对table布局的理解: 代码: <table cellspacing=0 border=1 style="bordercolor:#C0C0C0;" ...

  7. SpringCloud Feign使用详解

    添加依赖: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId> ...

  8. day2-Python基本数据类型介绍

    百度云连接 链接:https://pan.baidu.com/s/1hsGQx7m 密码:u07q

  9. Ubuntu 16.04 安装wine QQ

    1.进入 http://www.ubuntukylin.com/application/show.php?lang=cn&id=279下载Wine QQ 2.解压压缩包 3.将文件夹中三个de ...

  10. JaveScript简单数据类型(JS知识点归纳二)

    JS中的简单数据类型有五种 : --> string --> number -->boolean --> null -->undefined 数据类型的检测 :typeo ...