• 多线程实例(一)——遍历文件夹分割文件识别文件内容


    需求:遍历文件夹下的所有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要受到系统的限制,所以本例线程数必须考虑操作系统线程最大值限制。

  • 相关阅读:
    Linux常用命令
    ServerSocketChannel和SocketChannel
    Java扫描包
    [BZOJ3874/AHOI2014]宅男计划
    [BZOJ4029/HEOI2015]定价
    [考试]20151012贪心
    [BZOJ4027/HEOI2015]兔子与樱花
    [考试]20151010
    [考试]20151009
    Test of String
  • 原文地址:https://www.cnblogs.com/chaoyazhisi/p/13153460.html
Copyright © 2020-2023  润新知