您的位置:首頁>正文

使用Task的一些知識優化了一下同事的多執行緒協作取消的一串代碼

最近在看一個同事的代碼, 代碼的本意是在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 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 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感知到主

執行緒做了取消操作。

好了, 就說這麼多吧,

還是那句話, ”因為我們視野的不開闊, 導致缺乏解決問題的手段“, 所以古話說得好, 磨刀不誤砍柴工。 。 。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示