遇到一个多线程任务优化的问题,现在解决了,分享如下。

假设有四个任务:

任务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(100);
                            }
                            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. 老李分享:MySql的insert语句的性能优化方案

    老李分享:MySql的insert语句的性能优化方案   性能优化一直是测试人员比较感兴趣的内容,poptest在培训学员的时候也加大了性能测试调优的方面的内容,而性能优化需要经验的积累,经验的积累依 ...

  2. 优秀后端架构师必会知识:史上最全MySQL大表优化方案总结

    本文原作者“ manong”,原创发表于segmentfault,原文链接:segmentfault.com/a/1190000006158186 1.引言   MySQL作为开源技术的代表作之一,是 ...

  3. AngularJS应用页面切换优化方案

    葡萄城的一款尚在研发中的产品,对外名称暂定为X项目.其中使用了已经上市的Wijmo中SpreadJS产品,另外,在研发过程中整理了一些研发总结分享给大家.如本篇的在页面切换的过程中优化方案,欢迎大家跟 ...

  4. AngularJs应用页面切换优化方案(转)

    目录[-] 前言 场景 使用resolve来提前请求数据 为页面加入切换动画 总结 葡萄城的一款尚在研发中的产品,对外名称暂定为X项目.其中使用了已经上市的wijmo中SpreadJS产品,另外,在研 ...

  5. Adapter优化方案的探索

    概要:使用Adapter的注意事项与优化方案本文的例子都可以在结尾处的示例代码连接中看到并下载,如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request. ...

  6. 百度APP移动端网络深度优化实践分享(一):DNS优化篇

    本文由百度技术团队“蔡锐”原创发表于“百度App技术”公众号,原题为<百度App网络深度优化系列<一>DNS优化>,感谢原作者的无私分享. 一.前言 网络优化是客户端几大技术方 ...

  7. [转] 钉钉的H5性能优化方案

    对于一个H5的产品,功能无疑很重要,但是性能同样是用户体验中不可或缺的一环.原本H5的渲染性能就不及native的app,如果不把性能优化做起来,将极大地影响用户使用产品的积极性. 用户感受 当用户能 ...

  8. Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理

    Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理 2017年01月04日 08:52:12 阅读数:18366 基于Lucene检索引擎我们开发了自己的全文检索系统,承担起后台PB ...

  9. 数据库SQL优化大总结之 百万级数据库优化方案(转载)

    网上关于SQL优化的教程很多,但是比较杂乱.近日有空整理了一下,写出来跟大家分享一下,其中有错误和不足的地方,还请大家纠正补充. 这篇文章我花费了大量的时间查找资料.修改.排版,希望大家阅读之后,感觉 ...

随机推荐

  1. QSqlDatabase::addDatabase第一次运行的时候,生成SQLite文件的同时会产生一个默认连接

    QSqlDatabase::addDatabase第一次运行的时候,生成SQLite文件的同时会产生一个默认连接: QSqlDatabase database = QSqlDatabase::addD ...

  2. (二)----HTTP请求头与响应头

    一.HTTP头引入: 正确的设置HTTP头部信息有助于搜索引擎判断网页及提升网站访问速度.通常HTTP消息包括:客户机向服务器的请求消息和服务器向客户机的响应消 息.客户端向服务器发送一个请求,请求头 ...

  3. [ACM] hdu 4405 Aeroplane chess (概率DP)

    Aeroplane chess Problem Description Hzz loves aeroplane chess very much. The chess map contains N+1 ...

  4. Spire.XLS for .NET 測评

         有一位朋友推荐了我 Spire.Office (官网:http://www.e-iceblue.com/) -- 基于.NET的办公软件库,说不错. 究竟怎样呢?仅仅有亲測一下才干知道了. ...

  5. 设置Mysql的连接超时参数

     在Mysql的默认设置中,如果一个数据库连接超过8小时没有使用(闲置8小时,即   28800s),mysql server将主动断开这条连接,后续在该连接上进行的查询操作都将失败,将   出现:e ...

  6. DJ_Java_Decompiler新手入门教程

    首先声明:这篇文章并不是我原创,只是感觉挺有用处,想跟大家分享一下,所以标注为原创,希望能有更多的朋友可以看到,还请原作者谅解. 昨天大D说让我写下DJ入门的基础,今天写了一大半了,结果不小心把浏览器 ...

  7. OCP读书笔记(11) - 使用闪回技术II

    闪回归档 1. 什么是闪回数据归档? 闪回归档是用来保存一个或多个表的历史数据的新数据库对象,以及该数据的存储保留和清除策略.归档只是保存数据库中一个或多个表的所有事务处理的变化的一个或多个表空间,数 ...

  8. 实现自己的http server - loop_in_codes - C++博客

    实现自己的http server - loop_in_codes - C++博客 实现自己的http server Write your own http server author : Kevin ...

  9. 做SEO所要具备的四种能力

    1,不为失败找借口         既然我们选择了做SEO,那么发生网站被降权.被K是常常的事.当这样的情况发生时,大部分站长首先将责任推给百度机制,由于百度更新算法调整遭降权,不是由于他们的优化没有 ...

  10. 【ALearning】第二章 Androidproject知识介绍

    本章介绍了主要的初步Androidproject成立了一个开发环境.为了Android意识的整体项目和理解.本章包含Android开发环境的搭建.第一Android工程Hello World与Andr ...