C#读书笔记之并行任务
这两天遇到一个多线程任务优化的问题,现在解决了,把心得用例子的形式记下来。
假设有四个任务:
任务1:登陆验证(CheckUser)
任务2:验证成功后从Web服务获取数据(GetDataFromWeb)
任务3:验证成功后从数据库获取数据(GetDatFromDb)
任务4:使用2、3的数据执行一个方法 (StartProcess)
一个比较笨的方法(本人最开始的方法,记为方法1)是直接开启一个线程,按照顺序依次执行四个任务:
new Thread(delegate
{
CheckUser();
GetDatFromDb();//从数据库获取数据
GetDataFromWeb();//web服务获取数据
StartProcess();//执行4
}).Start();
但是仔细分析需求我们会发现,任务2和任务3并没有先后区别,事实上两者并无关联,只不过任务4的执行需要任务2和3都已完成作为条件,所以我们可以再开两个线程用于执行任务2和任务3,当两者都执行完毕之后,执行任务4。
在这里使用了两个全局变量用于表示任务2和任务3的状态。用三个线程分别执行任务2、3、4,其中任务4一直在循环监听全局变量的状态,确保在2、3都执行完毕后才执行。
这记为方法2:
private static volatile bool _m2;//任务2的标志位
private static volatile bool _m3;//任务3的标志位
private static void Main(string[] args)
{
new Thread(delegate
{
CheckUser();
new Thread(delegate
{
GetDatFromDb();//从数据库获取数据
_m2 = true;//标志位置为true
}).Start();
new Thread(delegate
{
GetDataFromWeb();//web服务获取数据
_m3 = true;//标志位置为true
}).Start();
new Thread(delegate
{
while (!(_m3 && _m2))//判断任务2和3是否已执行完毕
{
Thread.Sleep();
}
StartProcess();//执行任务4
_m2 = true;
}).Start();
}).Start();
}
以上代码基本上已经可以达到预期目标了,但是由于借助了两个全局变量,尽管在这里不会涉及到同步冲突的问题,但总觉得很不放心,而且当我们需要做扩展的时候,比方说在执行任务4之前,我们还需要加载文件内的数据(GetDataFromFile),那我们必须再添加一个全局标志位,显得有点麻烦。
事实上,Thread类本身已经拥有对这种情况的完美解决方案——join。
Thread.Join 方法
在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。
简单来说,join就是个阻塞方法,在线程1内创建线程2,调用线程2的Join方法,那么线程1将会被阻塞,直到线程2执行完毕。运用到上面的例子就是,在任务4内创建任务2和任务3的线程,调用任务2和任务3的线程的Join方法使任务4阻塞,直到任务2和任务3执行完毕,才继续执行任务4。这记为方法3,代码如下
private static void Main(string[] args)
{
new Thread(delegate
{
CheckUser();
new Thread(delegate
{
Thread task2 = new Thread(delegate
{
GetDatFromDb(); //从数据库获取数据
});
Thread task3 = new Thread(delegate
{
GetDataFromWeb(); //web服务获取数据
});
task2.Start();
task3.Start();
task2.Join();//任务2阻塞
task3.Join();//任务3阻塞
StartProcess(); //执行任务4
}).Start();
}).Start();
}
这样便不需要任何标志位了。这是最理想的解决方案。
另外还有一种解决方案,使用EventWaitHandle
EventWaitHandle 类允许线程通过发出信号和等待信号来互相通信。事件等待句柄(简称事件)就是可以通过发出相应的信号来释放一个或多个等待线程的等待句柄。信号发出后,可以用手动或自动方式重置事件等待句柄
简单来说,就是方法2的进阶版,使用EventWaitHandle控制状态,而不再使用While循环监听,但这里仍旧需要两个全局的EventWaitHandle对象。该方法记为方法4,代码如下
private static EventWaitHandle eventWait1 = new EventWaitHandle(false, EventResetMode.AutoReset);//初始化状态false;
private static EventWaitHandle eventWait2 = new EventWaitHandle(false, EventResetMode.AutoReset);//初始化状态false;
private static void Main(string[] args)
{
new Thread(delegate
{
CheckUser();
new Thread(delegate
{
GetDatFromDb(); //从数据库获取数据
eventWait1.Set(); //标志位置为true
}).Start();
new Thread(delegate
{
GetDataFromWeb(); //web服务获取数据
eventWait2.Set(); //标志位置为true
}).Start();
new Thread(delegate
{
eventWait1.WaitOne();//任务2阻塞,等待
eventWait2.WaitOne();//任务3阻塞,等待
StartProcess(); //执行任务4
}).Start();
}).Start();
}
上述三个优化方案,其实核心思想都是一样的,都是通过开启3个线程分别执行2、3、4任务,其中任务4被阻塞(while循环、eventWait.WaitOne,thread.join),当阻塞解除后,继续执行任务4。也就是说,任务4,其实是一直在等待任务2和任务3的完成。那么,是否有办法让任务2和任务3主动通知任务4呢?即,任务2和任务3完成后,主动执行任务4。
方法当然有:异步委托+回调函数
private static object obj = new object();
private static volatile bool _m2;//任务2的标志位
private static volatile bool _m3;//任务3的标志位 private static void Main(string[] args)
{
CheckUser(); //第一步 验证用户
Action step2 = delegate
{
GetDatFromDb(); //从数据库获取数据
_m2 = true; //标志位置为true
};
Action step3 = delegate
{
GetDataFromWeb(); //web服务获取数据
_m3 = true; //标志位置为true
}; step2.BeginInvoke(delegate
{
if (_m2 && _m3) //通过标志位判断2 3是否都已完成
{
lock (obj)//加锁
{
_m2 = false;
if (_m3)//二重验证 防止两者同时进入
StartProcess(); //执行4
}
}
}, null);
step3.BeginInvoke(delegate
{
if (_m2 && _m3) //通过标志位判断2 3是否都已完成
{
lock (obj)
{
_m3 = false;
if (_m2)
StartProcess(); //执行4
}
}
}, null);
}
讲解下代码。首先以委托的方式创建了任务2和任务3的委托对象step2和step3。执行这两个委托的异步调用方法BegInvoke。执行BegInvoke,会创建一个新的线程来执行step2和step3的方法,同时,在执行BeginInvoke的时候还指定了一个回调函数
delegate
{
if (_m2 && _m3) //通过标志位判断2 3是否都已完成
{
lock (obj)
{
_m3 = false;
if (_m2)
StartProcess(); //执行4
}
}
}
这个函数会在step2和step3的线程执行完毕后被调用。在这里,我再次使用了标志位来判断step2和step3是否已经运行完成,同时,为了防止一种特殊情:“step2和step3所执行的时间几乎相等,他们会同时通过if(_m2&&_m3)判断,进而执行两次StartProcess” 在这里加了lock锁,并且在lock锁内将标志位重置+二重判断(这里可以参考单例模式的双重锁定原理),确保StarProcess只会执行一次。
如此这般,一个主动通知模式的并行任务便实现了,不过,这种实现方法相较于方法2,实在太过麻烦,尤其在与并发处理方面,个人感觉实用性不太高。
另外,还可以使用观察者模式实现异步委托+回调函数的效果,具体代码就不写了。
C#读书笔记之并行任务的更多相关文章
- 《C#本质论》读书笔记(18)多线程处理
.NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...
- 《android开发艺术探索》读书笔记(十一)--Android的线程和线程池
接上篇<android开发艺术探索>读书笔记(十)--Android的消息机制 No1: 在Android中可以扮演线程角色的有很多,比如AsyncTask.IntentService.H ...
- 《Java编程思想》读书笔记(五)
前言:本文是<Java编程思想>读书笔记系列的最后一章,本章的内容很多,需要细读慢慢去理解,文中的示例最好在自己电脑上多运行几次,相关示例完整代码放在码云上了,码云地址:https://g ...
- 读书笔记汇总 - SQL必知必会(第4版)
本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...
- 读书笔记--SQL必知必会18--视图
读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...
- C#温故知新:《C#图解教程》读书笔记系列
一.此书到底何方神圣? 本书是广受赞誉C#图解教程的最新版本.作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式.朴实简洁的文字,并辅之以大量表格和代码示例,全面.直观地阐述了C#语言的各种 ...
- C#刨根究底:《你必须知道的.NET》读书笔记系列
一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...
- Web高级征程:《大型网站技术架构》读书笔记系列
一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...
- LOMA280保险原理读书笔记
LOMA是国际金融保险管理学院(Life Office Management Association)的英文简称.国际金融保险管理学院是一个保险和金融服务机构的国际组织,它的创建目的是为了促进信息交流 ...
随机推荐
- sqoop的eval工具
eval的作用:Evaluate a SQL statement and display the results,也就是说eval像是一个数据库的客户端工具. 一.使用eval来查询表 $ sqoop ...
- Jquery 实现json复杂查询等操作(jsonDB)
一.jsonDB 下载地址:https://github.com/ThinkerCodeChina/jsonDB jsonDB是js的一个类库,实现使用SQL语句对json数据增删改查.jsonDB的 ...
- PHP二维数组根据某个键名排序
$result = array( array( "amount": "11.00", "date": ...
- Win7、win2008中让IIS7支持asp的方法
Win7或Windows server 2008中IIS7支持ASP+Access解决方法. 1. 让IIS7支持ASP Win7或Windows server 2008中IIS7是默认不安装的, ...
- linux基本使用(一)
分区1./ 根分区2. swap 交换分区(大小建议是内存的1~2倍)3. /home 分区4./boot 引导文件(启动加载)分区5./var 等,最低 要有前2个分区吧,最好有home分区,因为没 ...
- 获取php的配置
ini_get — 获取一个配置选项的值 返回值 成功是返回配置选项值的字符串,null 的值则返回空字符串.如果配置选项不存在,将会返回 FALSE. <?php /* 我们的 php.ini ...
- WordPress 主题开发 - (八) Head模板 待翻译
THE WORDPRESS THEME HEADER TEMPLATE Now we get into the nitty-gritty: building up your header.php an ...
- gRPC Client的负载均衡器
一.gRPC是什么? gRPC是一个高性能.通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协 ...
- XAML(3) - 附带属性
WPF元素也可以从父元素中获得特性.例如,如果Button元素为了Canvas元素中,按钮的Top和Lef属性把父元素的名称作为前缀.这种属性成为附带属性: <Canvas> <Bu ...
- java reflect 初始学习 动态加载类
首先要理解Class类: 在java 的反射中,Class.forName("com.lilin.Office") 使用类的全名,这样获取,不仅仅表示了类的类类型,同时还代表着类的 ...