同时只允许Count个线程访问同一块区域的实现方式
好吧,后来才发现有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个线程访问同一块区域的实现方式的更多相关文章
- Winform之跨线程访问控件(在进度条上显示字体)
此文章对于遇到必须使用线程但是没有办法在线程内操作控件的问题的处理 有很好的解决方案(个人认为的.有更好的方案欢迎交流.) 在做跨线程访问之前我们先了解下我们所做的需要达到的效果: 这个是批量的将x ...
- [Winform]线程间操作无效,从不是创建控件的线程访问它的几个解决方案,async和await?
目录 概述 取消跨线程检查 使用委托异步调用 sync和await 总结 概述 最近在qq群里有一朋友,问起在winform中怎么通过开启线程的方式去处理耗时的操作,比如,查看某个目录下所有的文件,或 ...
- (委托事件处理)关于多线程执行显示进度条的实例(转)&&线程间操作无效: 从不是创建控件“rtxtEntryNO”的线程访问它。
关于多线程执行显示进度条的实例! 之前回答了一篇关于怎么在线程中操作进度条的帖子,估计有人看的不是很明白今天没事,写了一个小小的实例,很简单,就2个文件权当抛砖引玉,希望有更好解决方案的人发表一下意见 ...
- InvokeHelper,让跨线程访问/修改主界面控件不再麻烦(转)
http://bbs.csdn.net/topics/390162519 事实上,本文内容很简单且浅显,所以取消前戏,直接开始.. 源代码:在本文最后 这里是一张动画,演示在多线程(无限循环+Thre ...
- C#线程 访问资源同步简介
在多线程应用(一个或多个处理器)的计算中会使用到同步这个词.实际上,这些应用程序的特点就是它们拥有多个执行单元,而这些单元在访问资源的时候可能会发生冲突.线程间会共享同步对象,而同步对象的目的在于能够 ...
- Winform中子线程访问界面控件时被阻塞解决方案
public partial class WebData_Import : Form { //声明用于访问主界面的委托类型 public delegate void deleGetOrderdata( ...
- 线程间操作无效: 从不是创建控件“button1”的线程访问它。
.net2后是不能跨线程访问控件的.,窗体上的控件是当前线程创建的,当用户异步执行一个方法:在该方法中给窗体上的控件赋值,记住:当执行一个异步委托的时候,其实 就是开了一个线程去执行那个方法,这样就会 ...
- 【转】线程间操作无效: 从不是创建控件“textBox2” 的线程访问它。
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...
- wpf(怎么跨线程访问wpf控件)
在编写代码时,我们经常会碰到一些子线程中处理完的信息,需要通知另一个线程(我这边处理完了,该你了). 但是当我们通知WPF的UI线程时需要用到Dispatcher. 首先我们需要想好在UI控件上需要显 ...
随机推荐
- Kafka设计解析(八)- Exactly Once语义与事务机制原理
原创文章,首发自作者个人博客,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/transaction/ 写在前面的话 本 ...
- jstl--->Core 核心标签库->URL操作
jstl--->Core 核心标签库->URL操作 -->import.param.URL.redirect <c:import>将静态或动态文件包含至本身jsp页面 而 ...
- URL, URI, URN三者区别
URL和URN都是URI的子集 URL和URN都是URI,但是URI不一定是URL或者URN URI,URL,URN关系图 关于URL: URL是URI的一种,不仅标识了Web 资源,还指定了操作或者 ...
- YII2连表分页
控制器(controller)页面 use \yii\data\Pagination; //引入这个类 public function actionList(){ $data = Clock::fin ...
- 深入理解java虚拟机_第三章(上)----->垃圾收集器与内存分配策略
1. 前言 这一版块内容比较多,分为两篇文章来做笔记.本文讲述上半部分垃圾收集部分;下一篇文章写内存分配部分. 概述 对象已死吗? 引用技术算法 可达性分析算法 再谈引用 两次标记 回收方法区 2. ...
- 关于mui header在手机上运行丢失问题
并不需要换header, 只需要把引用的例子自带的CSS文件 app.css.里的两个样式:.mui-plus.mui-android header.mui-bar {display: none;}. ...
- cap deploy:setup报错
今天部署cap的时候,setup出现以下错误: 查询半天未果,不过最后还是在google找到了,可见度娘极为不靠谱! I had the same error on deploy:setup with ...
- Vue 浅谈前端js框架vue
Vue Vue近几年来特别的受关注,三年前的时候angularJS霸占前端JS框架市场很长时间,接着react框架横空出世,因为它有一个特性是虚拟DOM,从性能上碾轧angularJS,这个时候,vu ...
- hdoj 1285 确定比赛名次 【拓扑排序】
确定比赛名次 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Sub ...
- Android学习笔记(27):日历视图Calendar
日历视图CalendarView可用于显示和选择日期. 能够调用setOnDateChangedListener()方法绑定事件监听器. 经常使用XML属性和相关方法: XML属性 相关方法 说明 a ...