时间:2020-11-01 14:25:07 | 栏目:.NET代码 | 点击:次
遇到一个多线程任务优化的问题,现在解决了,分享如下。
假设有四个任务:
任务1:登陆验证(CheckUser)
任务2:验证成功后从Web服务获取数据(GetDataFromWeb)
任务3:验证成功后从数据库获取数据(GetDatFromDb)
任务4:使用2、3的数据执行一个方法 (StartProcess)
一个比较笨的方法(本人最开始的方法,记为方法1)是直接开启一个线程,按照顺序依次执行四个任务:
但是仔细分析需求我们会发现,任务2和任务3并没有先后区别,事实上两者并无关联,只不过任务4的执行需要任务2和3都已完成作为条件,所以我们可以再开两个线程用于执行任务2和任务3,当两者都执行完毕之后,执行任务4。
在这里使用了两个全局变量用于表示任务2和任务3的状态。用三个线程分别执行任务2、3、4,其中任务4一直在循环监听全局变量的状态,确保在2、3都执行完毕后才执行。
这记为方法2:
以上代码基本上已经可以达到预期目标了,但是由于借助了两个全局变量,尽管在这里不会涉及到同步冲突的问题,但总觉得很不放心,而且当我们需要做扩展的时候,比方说在执行任务4之前,我们还需要加载文件内的数据(GetDataFromFile),那我们必须再添加一个全局标志位,显得有点麻烦。
事实上,Thread类本身已经拥有对这种情况的完美解决方案――join。
简单来说,join就是个阻塞方法,在线程1内创建线程2,调用线程2的Join方法,那么线程1将会被阻塞,直到线程2执行完毕。运用到上面的例子就是,在任务4内创建任务2和任务3的线程,调用任务2和任务3的线程的Join方法使任务4阻塞,直到任务2和任务3执行完毕,才继续执行任务4。这记为方法3,代码如下
这样便不需要任何标志位了。这是最理想的解决方案。
另外还有一种解决方案,使用EventWaitHandle
简单来说,就是方法2的进阶版,使用EventWaitHandle控制状态,而不再使用While循环监听,但这里仍旧需要两个全局的EventWaitHandle对象。该方法记为方法4,代码如下
上述三个优化方案,其实核心思想都是一样的,都是通过开启3个线程分别执行2、3、4任务,其中任务4被阻塞(while循环、eventWait.WaitOne,thread.join),当阻塞解除后,继续执行任务4。也就是说,任务4,其实是一直在等待任务2和任务3的完成。那么,是否有办法让任务2和任务3主动通知任务4呢?即,任务2和任务3完成后,主动执行任务4。
方法当然有:异步委托+回调函数
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的时候还指定了一个回调函数
这个函数会在step2和step3的线程执行完毕后被调用。在这里,我再次使用了标志位来判断step2和step3是否已经运行完成,同时,为了防止一种特殊情:“step2和step3所执行的时间几乎相等,他们会同时通过if(_m2&&_m3)判断,进而执行两次StartProcess” 在这里加了lock锁,并且在lock锁内将标志位重置+二重判断(这里可以参考单例模式的双重锁定原理),确保StarProcess只会执行一次。
如此这般,一个主动通知模式的并行任务便实现了,不过,这种实现方法相较于方法2,实在太过麻烦,尤其在与并发处理方面,个人感觉实用性不太高。
另外,还可以使用观察者模式实现异步委托+回调函数的效果。