多線程實例——遍歷文件夾分割文件識別文件內容

多線程實例——遍歷文件夾分割文件識別文件內容

需求:遍歷文件夾下的所有pdf文件,對每個pdf文件根據二維碼進行分割,再對分割后的文件的內容進行識別。

可以拆分為以下幾個關鍵方法:

1.GetFileList方法:遍歷文件,獲取源文件動態數組(這裏假設3個文件夾,每個文件夾下有3個文件,則源文件個數為9),耗時忽略不計

 1 static List<string> GetFileList(string strFilefolder)
 2         {
 3             List<string> list_file = new List<string>();
 4 
 5             for (int i = 0; i <= 2; i++)
 6             {
 7                 for (int j = 0; j <= 2; j++)
 8                     list_file.Add("File" + i + j);
 9             }
10 
11             return list_file;
12         }

View Code

2.SplitProcess方法:分割原始pdf文件,識別二維碼(假設耗時500ms),將一個pdf文件分割為N(這裏假設個數為6)個子文件

 1 static void SplitProcess(string sourcefile)
 2         {
 3             Console.WriteLine("SplitFile Start:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
 4             for (int i = 0; i <= 5; i++)
 5             {
 6                 //模擬分割單個文件的過程,花費500ms
 7                 Thread.Sleep(500);
 8                 string split_file = sourcefile + i;
 9                 Console.WriteLine("file ready:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
10                 RecognizeProcess(split_file);
11             }
12             Console.WriteLine("SplitFile Completed:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
13         }

View Code

3.RecognizeProcess方法:識別子文件的內容:加載識別庫,設置識別參數,截取識別區域圖像,圖像處理(如縮放,降噪,灰度轉換等),識別(假設耗時5000ms

1 static void RecognizeProcess(string split_file)
2         {
3             //模擬識別的過程,花費5000ms
4             Thread.Sleep(5000);
5             Console.WriteLine("ocrFile Completed:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
6         }

View Code

單線程處理:

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("Enter Main" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 4             string strFilefolder = "";
 5             OcrProcess(strFilefolder);
 6             Console.WriteLine("Main Completed" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 7             Console.ReadKey();
 8         }
 9 
10         static void OcrProcess(string strFilefolder)
11         {            
12             List<string> list_sourcefile = GetFileList(strFilefolder);
13             list_sourcefile.ForEach((sourcefile) =>
14             {
15                 Console.WriteLine(sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
16                 //這裏對文件進行分割
17                 SplitProcess(sourcefile);
18             });            
19         }

View Code

這個單線程處理的執行結果我們可以預估一下,應該大於 9 * 6 * (0.5 + 5) = 297 秒。

實際結果:

 …… 

 開始時間 2020-06-17 15:22:28 6104 結束時間 2020-06-17 15:27:26 1541 

由於是線性處理,整個過程耗費的時間約5分鐘,所以必須要進行優化,所以考慮用多線程來提高效率。

優化方向:

1.多線程,使用Task并行對源文件進行分割 

 1 static void OcrProcess(string strFilefolder)
 2         {
 3             List<Task> tasks = new List<Task>();  
 4             List<string> list_sourcefile = GetFileList(strFilefolder);
 5             list_sourcefile.ForEach((sourcefile) =>
 6             {
 7                 Task task = Task.Factory.StartNew( () =>
 8                 { 
 9                     Console.WriteLine(sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
10                     //這裏對文件進行分割
11                     SplitProcess(sourcefile);
12                 });
13                 tasks.Add(task);                
14             });
15             Task.WaitAll(tasks.ToArray());
16         }

View Code

 ……

 

開始時間 2020-06-17 15:51:54 5458 結束時間 2020-06-17 15:52:35 3144 

整個過程耗費的時間約41秒,優化效果明顯。

2.每分割出來一個文件,開啟子線程,進行識別

 1 static void SplitProcess(string sourcefile)
 2         {
 3             List<Task> tasks = new List<Task>();
 4             Console.WriteLine("SplitFile Start:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
 5             for (int i = 0; i <= 5; i++)
 6             {
 7                 //模擬分割單個文件的過程,花費500ms
 8                 Thread.Sleep(500);
 9                 string split_file = sourcefile + i;
10                 Console.WriteLine("file ready:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
11                 Task task = Task.Factory.StartNew(() =>
12                 {
13                     RecognizeProcess(split_file);
14                 });
15                 tasks.Add(task);
16             }
17             Task.WaitAll(tasks.ToArray());
18             Console.WriteLine("SplitFile Completed:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
19         }

View Code

  ……

 開始時間 2020-06-17 15:58:59 2591 結束時間 2020-06-17 15:59:28 9051 

整個過程耗費的時間約29秒,運行時間進一步縮短。 

然而,最後再思考一下,如果把多線程發揮到極致,理想狀態應該是多少秒執行完畢?

以源文件sourcefile=File00為例,

第一個分割子文件split_file=File000,ReadyTime 500ms,Ocr Completed Time 應該為5500ms

第二個分割子文件split_file=File001,ReadyTime 1000ms,Ocr Completed Time 應該為6000ms 

第三個分割子文件split_file=File002,ReadyTime 1500ms,Ocr Completed Time 應該為6500ms 

第四個分割子文件split_file=File003,ReadyTime 2000ms,Ocr Completed Time 應該為7000ms

第五個分割子文件split_file=File004,ReadyTime 2500ms,Ocr Completed Time 應該為7500ms

第六個分割子文件split_file=File005,ReadyTime 3000ms,Ocr Completed Time 應該為8000ms

File00 Split Completed!

每一個源文件(sourcefile)被逐個分割為6個拆分子文件(split_file)並識別完成,都需要8000ms時間,如果使用線程同步的話,那麼後續源文件也同步被分割並識別完成。 

所以,理想情況下,應該是8秒,而與29秒差距太大了,應該還有優化空間!

3.怎麼優化?向什麼方向優化?我們不妨不用Task,回歸到Thread本身來試試。

可是Thread運行時沒有Task.WaitAll()這樣的控制方法,因此,我們還要引入WaitHandle和ManualResetEvent來進行多線程管理。 

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine("Enter Main" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 6             string strFilefolder = "";
 7             OcrProcess(strFilefolder);
 8             Console.WriteLine("Main Completed" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 9             Console.ReadKey();
10         }        
11 
12         static void OcrProcess(string strFilefolder)
13         {            
14             List<ManualResetEvent> split_waits = new List<ManualResetEvent>();
15             List<string> list_sourcefile = GetFileList(strFilefolder);
16             list_sourcefile.ForEach((sourcefile) =>
17             {
18                 Thread m_thread = new Thread(() =>
19                 {
20                     ManualResetEvent mre = new ManualResetEvent(false);
21                     split_waits.Add(mre);
22                     Console.WriteLine(sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
23                     //這裏對文件進行分割
24                     SplitProcess(sourcefile);
25                     mre.Set();
26                 });
27                 m_thread.Start();
28             });
29             WaitHandle.WaitAll(split_waits.ToArray());
30         }
31 
32         static void SplitProcess(string sourcefile)
33         {
34             Console.WriteLine("SplitFile Start:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
35             var ocr_waits = new List<EventWaitHandle>();
36             for (int i = 0; i <= 5; i++)
37             {
38                 //模擬分割單個文件的過程,花費500ms
39                 Thread.Sleep(500);
40                 string split_file = sourcefile + i;
41                 Console.WriteLine("file ready:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
42                 ManualResetEvent mre_child = new ManualResetEvent(false);
43                 ocr_waits.Add(mre_child);
44                 Thread m_child_thread = new Thread(() =>
45                 {
46                     Console.WriteLine("m_child_thread enter:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
47                     RecognizeProcess(split_file);                    
48                     mre_child.Set();
49                     Console.WriteLine("m_child_thread after set:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
50                 });
51                 m_child_thread.Start();
52             }
53             WaitHandle.WaitAll(ocr_waits.ToArray());
54             Console.WriteLine("SplitFile Completed:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
55         }
56 
57         static void RecognizeProcess(string split_file)
58         {
59             //模擬識別的過程,花費5000ms
60             Thread.Sleep(5000);
61             Console.WriteLine("ocrFile Completed:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
62         }
63 
64         static List<string> GetFileList(string strFilefolder)
65         {
66             List<string> list_file = new List<string>();
67             for (int i = 0; i <= 2; i++)
68             {
69                 for (int j = 0; j <= 2; j++)
70                     list_file.Add("File" + i + j);
71             }
72             return list_file;
73         }
74     }

View Code

  

  ……

開始時間 2020-06-17 15:28:17 2397 結束時間 2020-06-17 16:28:27 9151 

整個過程耗費的時間約10秒,運行時間與理論的8秒已經十分接近(因為Thread創建以及運行時需要切換上下文,Console.WriteLine都有一定的耗時,PC性能好的話應該更接近8秒),可以說目標已經達成。 

Tips:

ManualResetEvent初始狀態為false表示不將線程信號量初始值置為signal,線程會自動往下執行,執行Set()方法時,將線程信號量置為signal。

WaitHandle.WaitAll(split_waithandle1,split_waithandle2); //一直等待,直到split_waithandle1,split_waithandle2信號量均被置為signal才會往下執行。

不足之處:

開啟Thread要受到系統的限制,所以本例線程數必須考慮操作系統線程最大值限制。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!