使用Task的一些知识优化了一下同事的多线程协作…
2018-06-17 19:10:25来源:未知 阅读 ()
最近在看一个同事的代码,代码的本意是在main方法中开启10个线程,用这10个线程来处理一批业务逻辑,在某一时刻当你命令console退出的时候,这个
时候不是立即让console退出,而是需要等待10个线程把检测状态之后的业务逻辑执行完之后再退出,这样做是有道理的,如果强行退出会有可能造成子线程的业
务数据损坏,没毛病吧,业务逻辑大概就是这样。
一:现实场景
由于真实场景的代码比较复杂和繁琐,为了方便演示,我将同事所写的代码抽象一下,类似下面这样,看好了咯~~~
1 class Program 2 { 3 private static int workThreadNums = 0; 4 5 private static bool isStop = false; 6 7 static void Main(string[] args) 8 { 9 var tasks = new Task[10]; 10 11 12 for (int i = 0; i < 10; i++) 13 { 14 tasks[i] = Task.Factory.StartNew((obj) => 15 { 16 Run(); 17 }, i); 18 } 19 20 //是否退出 21 string input = Console.ReadLine(); 22 23 while ("Y".Equals(input, StringComparison.OrdinalIgnoreCase)) 24 { 25 break; 26 } 27 28 isStop = true; 29 30 while (workThreadNums != 0) 31 { 32 Console.WriteLine("正在等待线程结束,当前还在运行线程有:{0}", workThreadNums); 33 34 Thread.Sleep(10); 35 } 36 Console.WriteLine("准备退出了。。。"); 37 Console.Read(); 38 Environment.Exit(0); 39 } 40 41 static void Run() 42 { 43 try 44 { 45 workThreadNums++; 46 47 while (true) 48 { 49 if (isStop) break; 50 51 Thread.Sleep(1000); 52 53 //执行业务逻辑 54 Console.WriteLine("我是线程:{0},正在执行业务逻辑", Thread.CurrentThread.ManagedThreadId); 55 } 56 } 57 finally 58 { 59 workThreadNums--; 60 } 61 } 62 }
其实扫一下上面的代码应该就知道是用来干嘛的,业务逻辑没毛病,基本可以实现刚才的业务场景,在console退出的时候可以完全确保10个线程都把自己的业
务逻辑处理完毕了。不过从美观角度上来看,这种代码就太low了。。。一点档次都没有,比如存在下面两点问题:
第一点:局部变量太多,又是isStop又是workThreaNums,导致业务逻辑Run方法中掺杂了很多的非业务逻辑,可读性和维护性都比较low。
第二点:main函数在退出的时候用while检测workThreadNums是否为“0”,貌似没问题,但仔细想想这段代码有必要吗?
接下来我把代码跑一下,可以看到这个while检测到了在退出时的workThredNums的中间状态“7”,有点意思吧~~~
二:代码优化
那上面这段代码怎么优化呢?如何踢掉业务逻辑方法中的非业务代码呢?当然应该从业务逻辑上考虑一下了,其实这个问题的核心就是两点:
1. 如何实现多线程中的协作取消?
2. 如何实现多线程整体执行完毕通知主线程?
这种场景优化千万不要受到前人写的代码所影响,最好忘掉就更好了,不然你会下意识的受到什么workthreadnums,isstop这些变量的左右,不说废话了,如
果你对task并发模型很熟悉的话,你的优化方案很快就会出来的。。。
1. 协作取消:
直接用一个bool变量来判断子线程是否退出的办法其实是很没有档次的,在net 4.0中有一个类(CancellationTokenSource)专门来解决使用bool变量来判
断的这种很low的场景,而且比bool变量具有更强大的功能,这个会在以后的文章中跟大家去讲。
2. 多线程整体执行完毕通知主线程
目前我们看到的方式是主线程通过轮询workthreadnums这种没有档次的方式去做的,其实这种方式本质上就是任务串行,而如果你明白task的话,你就知道
有很多的手段是执行任务串行的,比如什么ContinueWith,WhenAll,WhenAny等等方式,所以你只需要将一组task串联到WhenAll之后就可以了。好了,上
面就是我的解决思路,接下来看一下代码吧:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 CancellationTokenSource source = new CancellationTokenSource(); 6 7 var tasks = new Task[10]; 8 9 for (int i = 0; i < 10; i++) 10 { 11 tasks[i] = Task.Factory.StartNew((m) => 12 { 13 Run(source.Token); 14 }, i); 15 } 16 17 Task.WhenAll(tasks).ContinueWith((t) => 18 { 19 Console.WriteLine("准备退出了。。。"); 20 Console.Read(); 21 Environment.Exit(0); 22 }); 23 24 string input = Console.ReadLine(); 25 while ("Y".Equals(input, StringComparison.OrdinalIgnoreCase)) 26 { 27 source.Cancel(); 28 } 29 30 Console.Read(); 31 } 32 33 static void Run(CancellationToken token) 34 { 35 while (true) 36 { 37 if (token.IsCancellationRequested) break; 38 39 Thread.Sleep(1000); 40 41 //执行业务逻辑 42 Console.WriteLine("我是线程:{0},正在执行业务逻辑", Thread.CurrentThread.ManagedThreadId); 43 } 44 } 45 }
单从代码量上面看就缩减了17行代码,而且业务逻辑也非常的简单明了,然后再看业务逻辑方法Run,其实你根本就不需要所谓的workThreadNums++,--
的操作,而且多线程下不用锁的话,还容易出现竞态的问题,解决方案就是使用WhenAll等待一组Tasks完成任务,之后再串行要退出的Task任务,是不是很完美,
而协作取消的话,只需将取消的token传递给业务逻辑方法,当主线程执行source.Cancel()方法取消的时候,子线程就会通过IsCancellationRequested感知到主
线程做了取消操作。
好了,就说这么多吧,还是那句话,”因为我们视野的不开阔,导致缺乏解决问题的手段“,所以古话说得好,磨刀不误砍柴工。。。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- Delphi10.3下自带系统托盘 TrayIcon的使用 2020-06-01
- delphi10.3安装使用mySQL 2020-05-31
- QF中间件 2020-02-04
- 人脸识别Demo 2019-08-29
- delphi使用Chilkat 组件和库从SFTP下载文件 2019-08-26
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash