这两天遇到一个多线程任务优化的问题,现在解决了,把心得用例子的形式记下来。

假设有四个任务:

任务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#读书笔记之并行任务的更多相关文章

  1. 《C#本质论》读书笔记(18)多线程处理

    .NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...

  2. 《android开发艺术探索》读书笔记(十一)--Android的线程和线程池

    接上篇<android开发艺术探索>读书笔记(十)--Android的消息机制 No1: 在Android中可以扮演线程角色的有很多,比如AsyncTask.IntentService.H ...

  3. 《Java编程思想》读书笔记(五)

    前言:本文是<Java编程思想>读书笔记系列的最后一章,本章的内容很多,需要细读慢慢去理解,文中的示例最好在自己电脑上多运行几次,相关示例完整代码放在码云上了,码云地址:https://g ...

  4. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

  5. 读书笔记--SQL必知必会18--视图

    读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...

  6. C#温故知新:《C#图解教程》读书笔记系列

    一.此书到底何方神圣? 本书是广受赞誉C#图解教程的最新版本.作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式.朴实简洁的文字,并辅之以大量表格和代码示例,全面.直观地阐述了C#语言的各种 ...

  7. C#刨根究底:《你必须知道的.NET》读书笔记系列

    一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...

  8. Web高级征程:《大型网站技术架构》读书笔记系列

    一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...

  9. LOMA280保险原理读书笔记

    LOMA是国际金融保险管理学院(Life Office Management Association)的英文简称.国际金融保险管理学院是一个保险和金融服务机构的国际组织,它的创建目的是为了促进信息交流 ...

随机推荐

  1. CentOS 7服务

    重启防火墙service firewalld start/restart/stop 使用systemctl来启动/停止/重启服务要启动一个服务,你需要使用如下命令:# systemctl start ...

  2. PHP高级笔记汇总

    一.PHP日期 PHP的date()函数用于格式化时间或日期.PHP Date()函数可把时间戳格式化为可读性更好的日期和时间.语法:date(format,timestamp)format:必需.规 ...

  3. Dev的DocumentManager 相关问题

    1.改变DocumentManager包含的窗体的排列方式 if (this.documentManager1.View.Type != ViewType.NativeMdi) { this.docu ...

  4. asp.net过滤HTML标签的几个函数

    以下是引用片段: ----- /**/ /// <summary> /// 去除HTML标记 /// </summary> /// <param name="N ...

  5. SDUST 作业10 Problem D 魔方阵

    Description 所谓N阶魔方阵,是一个N*N的方阵,其元素由1到N^2组成,且方阵每行每列以及对角线的元素和相等.如三阶魔方阵: 8 1 6 3 5 7 4 9 2     魔方阵的规律如下: ...

  6. 定时重启Apache与MySQL方法

    可以定时重启apache服务器等.让网站运行的效果更快. 采用at命令添加计划任务. 有关使用语法可以到window->“开始”->运行“cmd”->执行命令“at /”,这样界面中 ...

  7. js控制文本框只能输入数字 及 常用字符对应ASCII码值

    方法一: <INPUT TYPE='text' NAME=text onkeypress="a()"><script language=javascript> ...

  8. Storm入门学习随记

    推荐慕课网视频:http://www.imooc.com/video/10055 ====Storm的起源. Storm是开源的.分布式.流式计算系统 什么是分布式呢?就是将一个任务拆解给多个计算机去 ...

  9. Learning Scrapy笔记(三)- Scrapy基础

    摘要:本文介绍了Scrapy的基础爬取流程,也是最重要的部分 Scrapy的爬取流程 Scrapy的爬取流程可以概括为一个方程式:UR2IM,其含义如下图所示 URL:Scrapy的运行就从那个你想要 ...

  10. 第九章 管理类型(In .net4.5) 之 继承机制

    1. 概述 本章包括 设计和实现接口.创建和使用基类 以及 使用.net类库提供的标准接口. 2. 主要内容 2.1 设计和实现接口 一个接口包含公共的方法.属性.事件.索引器.类和结构都可以实现接口 ...