主要利用多线程异步的方式处理同一个文件—-线程池技术

参考链接:https://www.cnblogs.com/owenzh/p/13280211.html

概念总结:

1.并发:

—操作系统中,一个时间段内,有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行

两种并发关系分别是
1)互斥——进程间相互排斥的是使用临界资源的现象
临界资源:多个线程共享的资源(可以是内存中的一个变量,也可以是是一个文件)
2)同步—-进程之间的关系不是相互排斥临界资源的关系二十相互依赖的关系
eg:就是前一个进程的输出作为后一个进程的输入,当前一个进程没有输出是第二个进程必须等待

2.并行

并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。

3.多线程:

多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。

4.异步:

(同步-就是顺序执行,异步-就是彼此独立进行)
多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。

二、代码

Thread类实例化的时候需要提供有参或者无参的委托方法
一种方式是无参: Thread thread=new Thread(delegate() {});
或者ThreadStart threadStart = new ThreadStart(voidMethod);//需要一个无参的方法
然后Thread thread=new Thread(threadStart);
另一种方式是有参数的 Thread thread=new Thread(new Parameterized ThreadStart(Thread));

整体处理大文件的思路是:
利用多线程处理,将文件的字符行保存至内存,再经过自己的处理,编辑文本行,之后分配给多个文件(在本文件名同名的文件夹下,以1、2、3..数字命名的txt文件)保存
最后将这些文件合并至新文件,删除这些文件

我要做的功能是NMEA语句提取经纬度文件,但是NMEA文件内存大至30M,用主线程或者说单线程处理,就会出问题

  1. //用到的字段
  2. List<string> temp;//用来保存源文件的文本行
  3. private List<int> tempCounts = new List<int>();//用来装每个文件文本行个数
  4. private List<ParameterizedThreadStart> Methods = new List<ParameterizedThreadStart>();//带有参数的委托方法
  5. private List<Thread> Threads = new List<Thread>();//多线程
  6. private List<List<string>> tempStrings = new List<List<string>>();//每个临时文件中保存的字符串
  7. private List<string> Paths = new List<string>();//临时文件的路径
  8. int totalLines = 0;//
  9. private System.Timers.Timer tt;//线程开启的定时器
  10. private string file = "";//保存需要处理的文件名称
  1. private void btnOpenNMEA_ItemClick(object sender, ItemClickEventArgs e)
  2. {
  3. oFD.Multiselect = false;
  4. //打开文件
  5. if (oFD.ShowDialog() == DialogResult.OK)
  6. {
  7. //源文件路径
  8. string filePath = oFD.FileName;
  9. file = filePath;
  10. try
  11. {
  12. if (file == null)
  13. {
  14. XtraMessageBox.Show("路径不能为空", "MapSimulator", MessageBoxButtons.OK, MessageBoxIcon.Error); return;
  15. }
  16. if (!System.IO.File.Exists(file))
  17. {
  18. XtraMessageBox.Show("该文件不存在", "MapSimulator", MessageBoxButtons.OK, MessageBoxIcon.Error); return;
  19. }
  20. if (cbThreadNum.EditValue == null)
  21. {
  22. XtraMessageBox.Show("请选择线程数!");
  23. return;
  24. }
  25. //有参的线程启动函数//用的lambda表达式,要不然ReadTxt的参数必须为object
  26. ParameterizedThreadStart method = ob => ReadTxt(ob.ToString());
  27. Thread t = new Thread(method);//线程实例化
  28. t.Start(file);//线程开启
  29. t.Join(10);
  30. }
  31. catch (Exception es)
  32. {
  33. MessageBox.Show(es.ToString());
  34. }
  35. }
  36. }

ReadTxt函数即是保存源文件的文本行至内存,并且平均分配每次线程要保存的文本行数,最后一个线程保存的文本数需要注意,并且创建了与源文件同名的文件夹,里面保存名称为(1、2、3、4.txt)的文件
这里注意lambda表达式

  1. /// <summary>
  2. /// 多线程处理
  3. /// </summary>
  4. /// <param name="file"></param>
  5. private void ReadTxt(string file)
  6. {
  7. temp = File.ReadLines(file).ToList();
  8. totalLines = temp.Count;//文本行数
  9. int threadNums = (int)(cbThreadNum.EditValue);//线程数
  10. int perLine = (int)(temp.Count / threadNums);//每个线程处理的文本行数,最后一个线程处理的数量可能多余实际文本行数
  11. //{
  12. string Dir = Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file);
  13. if (!System.IO.File.Exists(Dir))
  14. {
  15. DirectoryInfo dir = new DirectoryInfo(Dir);
  16. dir.Create();//自行判断一下是否存在。
  17. }
  18. string path = Dir;//文件的上一目录+名字
  19. this.tempCounts.Clear();
  20. this.Methods.Clear();
  21. this.Threads.Clear();
  22. this.tempStrings.Clear();
  23. int i = 0;
  24. for (i = 0; i < threadNums - 1; i++)
  25. {
  26. List<string> tempS = temp.GetRange(i * perLine, perLine);
  27. tempStrings.Add(tempS);
  28. Paths.Add(path + "\\" + i.ToString() + ".txt");
  29. tempCounts.Add(0);
  30. }
  31. //最后一个线程--需要得到剩余的、不一定为perLine数量的文本行
  32. List<string> tempLast = temp.GetRange(perLine * (threadNums - 1), totalLines - perLine * (threadNums - 1));
  33. tempStrings.Add(tempLast);
  34. Paths.Add(path + "\\" + (threadNums - 1).ToString() + ".txt");
  35. tempCounts.Add(0);
  36. //开启多线程,并且执行对数据进行处理的函数
  37. for (i = 0; i < threadNums; i++)
  38. {
  39. List<string> strings = tempStrings[i];//每个文件块中的文本行内容
  40. ParameterizedThreadStart method = o => tempMakeDeal(ref strings, Paths[i]);
  41. Thread t = new Thread(method);
  42. Threads.Add(t);//开启一个线程即添加到变量
  43. t.Start();
  44. t.Join(100);
  45. }
  46. tt.Start();//处理完多线程分配文件内存之后,Elapsed事件来合并文件
  47. // }
  48. }

注意tt是定时器
在窗体的构造函数中写的
public Form1()
{
//添加线程选择项
repositoryItemComboBox3.Items.Add(4);
repositoryItemComboBox3.Items.Add(8);
repositoryItemComboBox3.Items.Add(16);
repositoryItemComboBox3.Items.Add(24);
repositoryItemComboBox3.Items.Add(32);
//
tt = new System.Timers.Timer();
tt.Interval = 60;
tt.Elapsed += T_Elapsed;//一段时间执行的函数
}

  1. private void T_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
  2. {
  3. int totalCount = 0;
  4. int threadNums = (int)(cbThreadNum.EditValue);
  5. for (int i = 0; i < threadNums; i++)
  6. {
  7. totalCount += tempCounts[i];//处理总行数
  8. }
  9. double percent = ((int)((double)totalCount / totalLines * 100));
  10. if (totalCount == totalLines)
  11. {
  12. temp.Clear();
  13. tt.Stop();
  14. string Dir = Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file);
  15. string outPutFileName = Path.GetFileNameWithoutExtension(file);
  16. string outPutFileNameWithPath = Dir + "\\" + outPutFileName + "LatLng.txt";
  17. outPutFileNameWithPath = ConfirmFile(outPutFileNameWithPath, FileAttributes.Normal, true);
  18. //合并
  19. string tempPath = Application.StartupPath;
  20. Thread.Sleep(500);
  21. string toLeft = "";
  22. for (int i = 0; i < Paths.Count; i++)
  23. {
  24. toLeft = Save(toLeft, Paths[i], outPutFileNameWithPath);
  25. Thread.Sleep(500);
  26. }
  27. XtraMessageBox.Show("提取成功,导出文件位于源文件同目录下!\r\n" + outPutFileName, "RT2NMEA", MessageBoxButtons.OK, MessageBoxIcon.Information);
  28. }
  29. }

tempMakeDeal方法。在这里执行处理文本块的函数

  1. private void tempMakeDeal(ref List<string> temp, string path)
  2. {
  3. string temps = "";
  4. path = ConfirmFile(path, FileAttributes.Hidden, true);
  5. int index = int.Parse(Path.GetFileNameWithoutExtension(path));//通过文件名后缀获取第几个文件
  6. var parser = new NmeaParser();//
  7. int num = tempCounts[index];//获取当前文件的文本行行数
  8. string[] list = new string[] { "$GPGGA", "$GNGGA", "$GLGGA" };
  9. foreach (string s in temp)
  10. {
  11. num++;
  12. tempCounts[index] = num;//用来记录处理了多少文本行
  13. if (s.Contains("$") && !s.Contains("%"))
  14. {
  15. //自定义处理文本行的方法,这里我写的是保存GGA语句
  16. if (list.Contains(s.Split(',')[0]))
  17. {
  18. var result = (GpggaMessage)parser.Parse(s);
  19. if(result.Latitude!=0)
  20. {
  21. //保存格式为:命令语句、经度、纬度、高程
  22. temps += s.Split(',')[0]+"\t"+ result.Longitude + "\t" + result.Latitude + "\t" + result.Altitude + "\r\n";
  23. }
  24. }
  25. }
  26. if (temps.Length > 8000)//向文件中写入的字符串长度不超过8000字符
  27. {
  28. File.AppendAllText(path, temps);
  29. temps = "";
  30. }
  31. }
  32. if (temps != "")
  33. {
  34. File.AppendAllText(path, temps);
  35. }
  36. }

这里ConfirmFile主要是指定文件的FileAttributes(Hidden/Normal)

  1. /// <summary>
  2. /// Set file attributes(Hidden/Normal)
  3. /// </summary>
  4. /// <param name="path"></param>
  5. /// <param name="attributes"></param>
  6. /// <param name="IsDeleted"></param>
  7. /// <returns></returns>
  8. public static string ConfirmFile(string path, FileAttributes attributes, bool IsDeleted)
  9. {
  10. if (!System.IO.File.Exists(path))
  11. {
  12. //没有则创建这个文件
  13. FileStream fs1 = new FileStream(path, FileMode.Create, FileAccess.Write);//创建写入文件 //设置文件属性为隐藏
  14. System.IO.File.SetAttributes(@path, attributes);//设置文件的属性(隐藏或者正常)
  15. StreamWriter sw = new StreamWriter(fs1);
  16. sw.Close();
  17. fs1.Close();
  18. return path;
  19. }
  20. if (IsDeleted)
  21. {
  22. File.Delete(path);
  23. //新建则创建这个文件
  24. FileStream fs1 = new FileStream(path, FileMode.Create, FileAccess.Write);//创建写入文件 //设置文件属性为隐藏
  25. System.IO.File.SetAttributes(@path, attributes);
  26. StreamWriter sw = new StreamWriter(fs1);
  27. sw.Close();
  28. fs1.Close();
  29. return path;
  30. }
  31. else
  32. {
  33. string outDir = Path.GetDirectoryName(path);
  34. string outName = Path.GetFileNameWithoutExtension(path) + "CreateByRT2NMEA" + Path.GetExtension(path);
  35. string outPutFileName = outDir + "\\" + outName;
  36. FileStream fs1 = new FileStream(outPutFileName, FileMode.Create, FileAccess.Write);//创建写入文件 //设置文件属性为隐藏
  37. System.IO.File.SetAttributes(@outPutFileName, attributes);
  38. StreamWriter sw = new StreamWriter(fs1);
  39. sw.Close();
  40. fs1.Close();
  41. return outPutFileName;
  42. }
  43. }