上週作了一個多線程處理大量文件的功能 一是記錄 二是分享 三是請博友指出不足 更多的瞭解多線程。html
1.任務:將大量(大約5G)一目錄下有日期規則命名的html文件按照年月日三個層次目錄存放,目的是爲了提升文件檢索效率。多線程
2.具體實現:開啓10個線程 將文件拷貝到目標文件夾;不符合要求的文件拷貝到別處;記錄錯誤信息和不符合要求的信息;循環判斷狀態 執行完畢給出提示。spa
3.開始設想和後來出現問題:線程
開了10個線程 處理全部文件,若是一文件已在目標文件下存在 則不處理,可是線程間幾乎是同時進行的 這樣就會報錯"該文件已被另外一線程佔用"。這樣處理是不行的 因而就改善了。讓每一個線程按順序處理固定條數的文件,則每一個線程不會處理處理同個文件。code
4.代碼實現:orm
聲明變量htm
/ /App.config 配置路徑 //源文件路徑 string sourcePath = ConfigurationManager.AppSettings["sourcePath"]; //目標路徑 string toPath = ConfigurationManager.AppSettings["toPath"]; //錯誤文件存放路徑 string errorLogPath = "\\log"; //文件總數 int totalFileCount; //複製文件數 private int operaFileCount; //未複製文件數 int notCopyFileCount; //文件名稱過長文件數 int longNameFileCount; //線程數 int threadCount = 10;
將全部文件名稱進行分批處理 分頁方法
/// <summary> /// 將全部文件名稱進行分頁 /// </summary> /// <param name="PageSize">每頁條數</param> /// <param name="CurPage">當前頁</param> /// <param name="objs">集合</param> /// <returns></returns> public static List<string> QueryByPage(int PageSize, int CurPage, List<string> objs) { return objs.Take(PageSize * CurPage).Skip(PageSize * (CurPage - 1)).ToList(); } public class Page { public int pageIndex { get; set; } public int pageCount { get; set; } public int pageSize { get; set; } public List<string> list { get; set; } }
拷貝文件的方法blog
#region DoWork方法 private string errorFieName; public void DoWork(object obj) { Object locker = new object(); Page page = (Page)obj; Console.WriteLine(ThreadName()); int PageSize = page.pageSize; int CurPage = page.pageIndex; var grouList = QueryByPage(PageSize, CurPage, page.list); foreach (string file in grouList) { string fileName = Path.GetFileName(file); errorFieName = fileName; if (fileName != null && fileName.Length != 18) { Console.WriteLine(fileName + "文件名稱不符合要求!"); CopyErrorFile(fileName); Write(fileName + "文件名稱不符合要求!"); continue; } //Console.WriteLine(fileName.Length); try { //截取文件名稱 源文件名稱規則 ABC2014200...html 意思是:2014年第200天 能夠推算出年月日 string subYearMonth = fileName.Substring(3, 5); int yearNum = 0; int dayNum = 0; int.TryParse(subYearMonth.Substring(0, 2), out yearNum); int.TryParse(subYearMonth.Substring(2, 3), out dayNum); int.TryParse("20" + yearNum, out yearNum); if (yearNum < 1 || dayNum < 1) { Console.WriteLine(fileName + "文件名稱不符合要求!"); CopyErrorFile(fileName); //Write(fileName + "文件名稱不符合要求!"); continue; } //聲明日期 DateTime date = new DateTime(); date = date.AddYears(yearNum).AddYears(-1); date = date.AddDays(dayNum).AddDays(-1); string fullSavePath = string.Format("{0}\\{1}\\{2}\\{3}\\", toPath, yearNum, date.Month, date.Day); if (!Directory.Exists(fullSavePath)) { Directory.CreateDirectory(fullSavePath); } lock (fullSavePath) { File.Copy(file, fullSavePath + fileName, true); operaFileCount++; //Write("處理完成:" + fileName); } } catch (Exception ex) { Console.WriteLine("文件名稱:" + errorFieName + "處理錯誤:" + ex.Message); Write(ex.Message); throw new Exception(ex.Message); } } } #endregion
循環執行線程ip
public void CopyFile() { //開始方法時刪除上次記錄 DeleteLog(); List<Thread> threadList = new List<Thread>(); ; for (int i = 0; i < threadCount; i++) { try { if (!Directory.Exists(toPath)) { Directory.CreateDirectory(toPath); } //string[] fileNames = Directory.GetFileSystemEntries(sourcePath); string[] fileNames = Directory.GetFiles(sourcePath); var fileNameList = fileNames.ToList(); totalFileCount=fileNames.Length; //共threadCount個線程 每一個線程執行oneThreadNameCount條 int oneThreadNameCount = (int)Math.Ceiling(((double)fileNames.Length) / threadCount); Page page; //當前執行的線程 page = new Page { pageIndex = i + 1, pageSize = oneThreadNameCount, list = fileNameList }; threadList.Add(new Thread(new ParameterizedThreadStart(DoWork))); threadList[i].Start(page); threadList[i].Name = i + "_thread"; } catch (Exception ex) { Console.WriteLine("錯誤信息:" + ex.Message); Write(ex.Message); } } //判斷線程執行狀況 bool isRanning; bool isComplete = false; while (!isComplete) { //2秒判斷一次 Thread.Sleep(2000); isRanning = false; foreach (var item in threadList) { if (item.ThreadState == ThreadState.Running) { isRanning = true; } } if (!isRanning) { isComplete = true; Console.WriteLine(); Console.WriteLine("處理結果 共" + totalFileCount+";已處理:"+operaFileCount); Console.WriteLine("不符合要求:" + notCopyFileCount + "(已拷貝到errorfile文件夾);沒法拷貝名稱過長文件:" + longNameFileCount); Console.WriteLine("請查看文件夾" + toPath); } } Console.ReadKey(); }
輔助方法get
#region copy不符合要求文件 private void CopyErrorFile(string fileName) { //實際長度超過222報錯,而且沒法拋出異常 if (fileName.Length > 180) { longNameFileCount += 1; Write(fileName + "名稱過長!"); return; } notCopyFileCount += 1; string strErrorPath =toPath+"\\errorfile"; if(!Directory.Exists(strErrorPath)) Directory.CreateDirectory(strErrorPath); File.Copy(sourcePath+"\\"+fileName, toPath+"\\errorfile\\" + fileName, true); } #endregion private void DeleteLog() { try { if (!Directory.Exists(toPath + errorLogPath)) return; foreach (string var in Directory.GetFiles(toPath + errorLogPath)) { File.Delete(var); } } catch { //Directory.CreateDirectory(toPath + errorLogPath); } } private void Write(string message) { object obj = new object(); lock (obj) { try { string logPath=toPath+errorLogPath; if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath); Thread primaryThread = Thread.CurrentThread; //當前線程名稱 string threadName = primaryThread.Name; using (TextWriter sw = new StreamWriter(logPath+"\\"+ThreadName()+"_error.txt", true)) { sw.WriteLine("錯誤信息:" + message); sw.Dispose(); } } catch (Exception ex) { Console.WriteLine("錯誤信息:" + ex.Message); } } }