主要利用多线程异步的方式处理同一个文件—-线程池技术
参考链接: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,用主线程或者说单线程处理,就会出问题
//用到的字段
List<string> temp;//用来保存源文件的文本行
private List<int> tempCounts = new List<int>();//用来装每个文件文本行个数
private List<ParameterizedThreadStart> Methods = new List<ParameterizedThreadStart>();//带有参数的委托方法
private List<Thread> Threads = new List<Thread>();//多线程
private List<List<string>> tempStrings = new List<List<string>>();//每个临时文件中保存的字符串
private List<string> Paths = new List<string>();//临时文件的路径
int totalLines = 0;//
private System.Timers.Timer tt;//线程开启的定时器
private string file = "";//保存需要处理的文件名称
private void btnOpenNMEA_ItemClick(object sender, ItemClickEventArgs e)
{
oFD.Multiselect = false;
//打开文件
if (oFD.ShowDialog() == DialogResult.OK)
{
//源文件路径
string filePath = oFD.FileName;
file = filePath;
try
{
if (file == null)
{
XtraMessageBox.Show("路径不能为空", "MapSimulator", MessageBoxButtons.OK, MessageBoxIcon.Error); return;
}
if (!System.IO.File.Exists(file))
{
XtraMessageBox.Show("该文件不存在", "MapSimulator", MessageBoxButtons.OK, MessageBoxIcon.Error); return;
}
if (cbThreadNum.EditValue == null)
{
XtraMessageBox.Show("请选择线程数!");
return;
}
//有参的线程启动函数//用的lambda表达式,要不然ReadTxt的参数必须为object
ParameterizedThreadStart method = ob => ReadTxt(ob.ToString());
Thread t = new Thread(method);//线程实例化
t.Start(file);//线程开启
t.Join(10);
}
catch (Exception es)
{
MessageBox.Show(es.ToString());
}
}
}
ReadTxt函数即是保存源文件的文本行至内存,并且平均分配每次线程要保存的文本行数,最后一个线程保存的文本数需要注意,并且创建了与源文件同名的文件夹,里面保存名称为(1、2、3、4.txt)的文件
这里注意lambda表达式
/// <summary>
/// 多线程处理
/// </summary>
/// <param name="file"></param>
private void ReadTxt(string file)
{
temp = File.ReadLines(file).ToList();
totalLines = temp.Count;//文本行数
int threadNums = (int)(cbThreadNum.EditValue);//线程数
int perLine = (int)(temp.Count / threadNums);//每个线程处理的文本行数,最后一个线程处理的数量可能多余实际文本行数
//{
string Dir = Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file);
if (!System.IO.File.Exists(Dir))
{
DirectoryInfo dir = new DirectoryInfo(Dir);
dir.Create();//自行判断一下是否存在。
}
string path = Dir;//文件的上一目录+名字
this.tempCounts.Clear();
this.Methods.Clear();
this.Threads.Clear();
this.tempStrings.Clear();
int i = 0;
for (i = 0; i < threadNums - 1; i++)
{
List<string> tempS = temp.GetRange(i * perLine, perLine);
tempStrings.Add(tempS);
Paths.Add(path + "\\" + i.ToString() + ".txt");
tempCounts.Add(0);
}
//最后一个线程--需要得到剩余的、不一定为perLine数量的文本行
List<string> tempLast = temp.GetRange(perLine * (threadNums - 1), totalLines - perLine * (threadNums - 1));
tempStrings.Add(tempLast);
Paths.Add(path + "\\" + (threadNums - 1).ToString() + ".txt");
tempCounts.Add(0);
//开启多线程,并且执行对数据进行处理的函数
for (i = 0; i < threadNums; i++)
{
List<string> strings = tempStrings[i];//每个文件块中的文本行内容
ParameterizedThreadStart method = o => tempMakeDeal(ref strings, Paths[i]);
Thread t = new Thread(method);
Threads.Add(t);//开启一个线程即添加到变量
t.Start();
t.Join(100);
}
tt.Start();//处理完多线程分配文件内存之后,Elapsed事件来合并文件
// }
}
注意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;//一段时间执行的函数
}
private void T_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
int totalCount = 0;
int threadNums = (int)(cbThreadNum.EditValue);
for (int i = 0; i < threadNums; i++)
{
totalCount += tempCounts[i];//处理总行数
}
double percent = ((int)((double)totalCount / totalLines * 100));
if (totalCount == totalLines)
{
temp.Clear();
tt.Stop();
string Dir = Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file);
string outPutFileName = Path.GetFileNameWithoutExtension(file);
string outPutFileNameWithPath = Dir + "\\" + outPutFileName + "LatLng.txt";
outPutFileNameWithPath = ConfirmFile(outPutFileNameWithPath, FileAttributes.Normal, true);
//合并
string tempPath = Application.StartupPath;
Thread.Sleep(500);
string toLeft = "";
for (int i = 0; i < Paths.Count; i++)
{
toLeft = Save(toLeft, Paths[i], outPutFileNameWithPath);
Thread.Sleep(500);
}
XtraMessageBox.Show("提取成功,导出文件位于源文件同目录下!\r\n" + outPutFileName, "RT2NMEA", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
tempMakeDeal方法。在这里执行处理文本块的函数
private void tempMakeDeal(ref List<string> temp, string path)
{
string temps = "";
path = ConfirmFile(path, FileAttributes.Hidden, true);
int index = int.Parse(Path.GetFileNameWithoutExtension(path));//通过文件名后缀获取第几个文件
var parser = new NmeaParser();//
int num = tempCounts[index];//获取当前文件的文本行行数
string[] list = new string[] { "$GPGGA", "$GNGGA", "$GLGGA" };
foreach (string s in temp)
{
num++;
tempCounts[index] = num;//用来记录处理了多少文本行
if (s.Contains("$") && !s.Contains("%"))
{
//自定义处理文本行的方法,这里我写的是保存GGA语句
if (list.Contains(s.Split(',')[0]))
{
var result = (GpggaMessage)parser.Parse(s);
if(result.Latitude!=0)
{
//保存格式为:命令语句、经度、纬度、高程
temps += s.Split(',')[0]+"\t"+ result.Longitude + "\t" + result.Latitude + "\t" + result.Altitude + "\r\n";
}
}
}
if (temps.Length > 8000)//向文件中写入的字符串长度不超过8000字符
{
File.AppendAllText(path, temps);
temps = "";
}
}
if (temps != "")
{
File.AppendAllText(path, temps);
}
}
这里ConfirmFile主要是指定文件的FileAttributes(Hidden/Normal)
/// <summary>
/// Set file attributes(Hidden/Normal)
/// </summary>
/// <param name="path"></param>
/// <param name="attributes"></param>
/// <param name="IsDeleted"></param>
/// <returns></returns>
public static string ConfirmFile(string path, FileAttributes attributes, bool IsDeleted)
{
if (!System.IO.File.Exists(path))
{
//没有则创建这个文件
FileStream fs1 = new FileStream(path, FileMode.Create, FileAccess.Write);//创建写入文件 //设置文件属性为隐藏
System.IO.File.SetAttributes(@path, attributes);//设置文件的属性(隐藏或者正常)
StreamWriter sw = new StreamWriter(fs1);
sw.Close();
fs1.Close();
return path;
}
if (IsDeleted)
{
File.Delete(path);
//新建则创建这个文件
FileStream fs1 = new FileStream(path, FileMode.Create, FileAccess.Write);//创建写入文件 //设置文件属性为隐藏
System.IO.File.SetAttributes(@path, attributes);
StreamWriter sw = new StreamWriter(fs1);
sw.Close();
fs1.Close();
return path;
}
else
{
string outDir = Path.GetDirectoryName(path);
string outName = Path.GetFileNameWithoutExtension(path) + "CreateByRT2NMEA" + Path.GetExtension(path);
string outPutFileName = outDir + "\\" + outName;
FileStream fs1 = new FileStream(outPutFileName, FileMode.Create, FileAccess.Write);//创建写入文件 //设置文件属性为隐藏
System.IO.File.SetAttributes(@outPutFileName, attributes);
StreamWriter sw = new StreamWriter(fs1);
sw.Close();
fs1.Close();
return outPutFileName;
}
}