有四个任务 任务1:登陆验证 任务2:验证成功后从Web服务获取数据 任务3:验证成功后从数据库获取数据 任务4:使用2、3的数据执行一个方法
在解决上述问题中,最早的方法是使用顺序执行1、2、3、4,现在对此做了优化,由于2、3两个任务之间是没有关联的,可以放到不同线程内执行,但是4的执行需要2、3同时完成作为条件,因此,在1执行后,创建两个全局标志位M2,M3,开启三个线程分别执行2、3、4,其中2、3执行完毕后会分别把M2、M3置为true,4的线程一直循环监听M2、M3,当两者都为true时开始执行4的任务。
现在想把这个方法做进一步优化,不希望4一直在询问2、3是否完成了,而是2、3完成了自己通知4。这里想到了使用异步委托BeginInvoke,创建一个4的回调函数,但难点是需要两个任务都执行完毕后才执行这个回调函数。
很自然地,我想到了再一次使用全局变量M2,M3。不过总觉得这种方式不太优雅?是否有更好的解决方案呢?
另外,想请教下这种类型的问题该归纳为那种问题,不太像并发问题吧?
1 、Thread Join
2、Event Wait
感谢。
Thread.Join 是个好办法
Event Wait的话是需要创建两个全局的EventWaitHandle吧,效果没有ThreadJoin好。
两者的核心思想都是阻塞4任务的线程 等待2 3任务的线程执行完毕再执行,使我第一次优化的进阶版本。
不够我觉得我使用异步委托+回调函数也是个挺不错的想法。
@林J: 2,3是两个并行的任务,4 要等待 2,3同时完成后才能执行,那么你就需要一种机制来表示,你用全局变量 M2,M3的性能表现很差,因为你需要循环读取M2,M3的值来判断2,3是否都完成了,event 是一种更简洁、高效的通知机制。还有种方式,就是用 M2 和 spinwait 来实现,spinwait 比 event 更高效,但是这只适合等待时间较短的情况。还有,如果你使用 M2,M3的话,你需要定义为 volatile 。
@Launcher: 这点我知道,所以我改进成了使用异步委托+回调函数的方式。
private static volatile bool _m2; private static volatile bool _m3; private static void Main(string[] args) { DateTime dateTime = DateTime.Now; Console.WriteLine(dateTime); CheckUser("zong","zong");//第一步 验证用户 Action step2 = delegate { GetDatFromDb();//从数据库获取数据 _m2 = true;//标志位置为true }; Action step3 = delegate { GetDataFromWeb();//web服务获取数据 _m3 = true;//标志位置为true }; step2.BeginInvoke(delegate { if (_m2 && _m3)//通过标志位判断2 3是否都已完成 { StartProcess(dateTime);//执行4 } }, null); step3.BeginInvoke(delegate { if (_m2 && _m3)//通过标志位判断2 3是否都已完成 { StartProcess(dateTime);//执行4 } }, null); Console.Read(); }
@林J: 你这个程序很巧妙就在于,你用了 Console.Read(); 所以省去了循环检测 m2,m3 的代码。使用 Console.Read 和同使用 Thread join 是一样的。但是你的代码有个BUG,就是可能执行两次 StartProcess(dateTime)。你可以自己分析下。
@Launcher: Console.Read可以看成是主线程自己要执行的其他事情,使用Thread Join的话,就会造成主线程阻塞了,这两者是不一样的,事实上检测 m2,m3的状态只会在进入BeginInvok的回调函数的时候检测一次而已,总共两次。当GetDataFromWeb()和StartProcess(dateTime)所花费时间非常相近的时候,StartProcess可能会执行两次,这我已经想到过了,可以通过在进入回调函数之后重置标志位+lock解决。
@林J:
step2.BeginInvoke(delegate { if (_m2 && _m3)//通过标志位判断2 3是否都已完成 { lock (obj) { _m2 = false; if (_m3) StartProcess(dateTime);//执行4 } } }, null); step3.BeginInvoke(delegate { if (_m2 && _m3) //通过标志位判断2 3是否都已完成 { lock (obj) { _m3 = false; if (_m2) StartProcess(dateTime); //执行4 } } }, null);
重置标志位+lock 不过这样看来实现起来太过复杂 还是你提供的两种方法最实用。
@林J: 你的 Console.Read 后面必须接收一个 std 输入才能继续执行,但是在 main 退出前,你总是要等待 2,3 执行完成。单从你这段代码看,没觉得有啥区别,除非你能把 Console.Read 后面的还有同步执行的代码。Console.Read 同样是等待 std handle 上的一个事件被激活,也就是说,你仍然使用了 event 来同步,只是你不知道,如果将此程序放置在非 Console 程序中,你就应该明白。
如果你采用 lock,那么你同样需要一个全局锁,这样你就有三个变量 m2,m3,lock,如果使用 event,你需要一个 event 变量,在 step3 中 set event,然后在 step4 回调中 wait event,当然,这两个顺序可以颠倒,不影响结果。
当然上面的建议,并没涉及到使用异步IO来提高性能。
@Launcher:
这跟Console.Read一点关系都没吧,在上述代码中,step2 step3 step4分别在不同线程内执行,主线程一直无阻塞,可以把Console.Read换成
while (true) { Console.WriteLine("do other thing" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(200); }
假如实用Thread Join模式,线程肯定会有阻塞的。
@林J: 那你就按你的思路去理解吧。
亲,有好几个办法可以解决你的问题,涉及到线程同步的
你可以去MSDN下面看一下Thread下面的方法,或是System.Threading命名空间下的类,就可以轻松解决你的问题.