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

一線碼農發表於2017-03-13

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

執行緒做了取消操作。

 

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

相關文章