需求:遍歷文件夾下的全部pdf文件,對每一個pdf文件根據二維碼進行分割,再對分割後的文件的內容進行識別。數組
能夠拆分爲如下幾個關鍵方法:多線程
1.GetFileList方法:遍歷文件,獲取源文件動態數組(這裏假設3個文件夾,每一個文件夾下有3個文件,則源文件個數爲9),耗時忽略不計ide
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 }
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 }
3.RecognizeProcess方法:識別子文件的內容:加載識別庫,設置識別參數,截取識別區域圖像,圖像處理(如縮放,降噪,灰度轉換等),識別(假設耗時5000ms)spa
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 }
單線程處理:操作系統
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 }
這個單線程處理的執行結果咱們能夠預估如下,應該大於 9 * 6 * (0.5 + 5) = 297 秒。線程
實際結果:3d
…… code
開始時間 2020-06-17 15:22:28 6104 結束時間 2020-06-17 15:27:26 1541 blog
因爲是線性處理,整個過程耗費的時間約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 }
……
開始時間 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 }
……
開始時間 2020-06-17 15:58:59 2591 結束時間 2020-06-17 15:59:28 9051
整個過程耗費的時間約29秒,運行時間進一步縮短。
然而,最後再思考一下,若是把多線程發揮到極致,理想狀態應該是多少秒執行完畢?
500ms:第一個文件被分割出來,在這個時間裏,線程同步的話,那麼後續文件也一併被分割出來了。
5500ms:第一個分割文件已識別,在這個時間裏,線程同步的話,後續文件應該也一併都被識別出來了。
因此,理想狀況下,應該是5.5秒,而與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 }
……
開始時間 2020-06-17 15:28:17 2397 結束時間 2020-06-17 16:28:27 9151
整個過程耗費的時間約10秒,運行時間與理論的5.5秒已經十分接近(由於Thread切換上下文,Console.WriteLine都有必定的耗時),能夠說目標已經達成。
Tips:
ManualResetEvent初始狀態爲false表示不將線程信號量初始值置爲signal,線程會自動往下執行,執行Set()方法時,將線程信號量置爲signal。
WaitHandle.WaitAll(split_waithandle1,split_waithandle2); //一直等待,直到split_waithandle1,split_waithandle2信號量均被置爲signal纔會往下執行。
不足之處:
開啓Thread要受到系統的限制,因此本例線程數必須考慮操做系統線程最大值限制。