現在我們要集中精力實現一個實戰實例來描述到目前為止我們已經看過的內容。這里要實現的DataImport 例子是那種等待文件到達指定目錄然后將其導入到一個SQL Server 數據庫中的典型程序。下面我們列出了這個例子中將要使用的類:
FileSystemWatcher: 這個類允許開發人員監控指定目錄並能夠在發生改變時(比如創建一個新文件或者刪除一個文件)觸發事件。這個類位於System.IO 命名空間中。
TextWriterTraceListener: 實現我們自己的跟蹤功能。
Thread: 已經看過很多遍了,允許我們啟動一個新線程來把數據導入到數據庫中。
很多SqlClient 命名空間里的類,用於管理SQL Server 數據庫連接和更新。
第一版的DataImport 故意加了點邏輯錯誤,這樣你就可以使用跟蹤功能的日志文件並了解其重要性。
代碼如下:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.IO; using System.Threading; namespace DataImport { class DataImport { public static BooleanSwitch bs; static FileSystemWatcher fsw; [STAThread] static void Main(string[] args) { //Remove the default listener Trace.Listeners.RemoveAt(0); //Create and add the new listener bs = new BooleanSwitch("DISwitch", "DataImport switch"); Trace.Listeners.Add(new TextWriterTraceListener( new FileStream(@"D:\DataImport.log", FileMode.OpenOrCreate))); //Create a FileSystemWatcher object used to monitor the specified directory fsw = new FileSystemWatcher(); fsw.Path = @"D:\Temp"; fsw.Filter = "*.xml"; fsw.IncludeSubdirectories = false; fsw.Created += new FileSystemEventHandler(OnFileCreated); fsw.EnableRaisingEvents = true; try { WaitForChangedResult res; while (true) { res = fsw.WaitForChanged(WatcherChangeTypes.Created); Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- Found: " + res.Name + " file"); } } catch (Exception ex) { Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- An exception occured while waiting for file :"); Trace.Indent(); Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- " + ex.ToString()); Trace.Unindent(); } finally { fsw.EnableRaisingEvents = false; Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- Directory monitoring stopped"); Trace.Close(); } } static void OnFileCreated(object sender, FileSystemEventArgs e) { try { //Create a new object from the ImportData class to process the incoming file ImportData id = new ImportData(); id.mStrFileName = e.FullPath; Thread t = new Thread(new ThreadStart(id.Import)); t.Name = "DataImportThread"; t.Start(); } catch (Exception ex) { Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- An exception occured while queuing file:"); Trace.Indent(); Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- " + ex.ToString()); Trace.Unindent(); } finally { Trace.Flush(); } } } }
測試程序
按照以下步驟測試程序:
創建一個C:\Temp 目錄來存儲XML 文件
運行DataImport 程序
把authors.xml 文件放到C:\Temp 目錄中
最終結果可以從DataImport.log 文件中找到,結果如下:
2012/4/28 13:41:20- Found: Authors.xml file
2012/4/28 13:41:20- Filling the DataSet
2012/4/28 13:41:20- Reading XML file
2012/4/28 13:41:20- DatSet filled with data.
2012/4/28 13:41:20- Updating database...
2012/4/28 13:41:20- Database updated successfully.
2012/4/28 13:41:23- Total TIME: 00:00:02.0370000 seconds
authors.xml 文件不大,整個運行時間非常短。
邏輯錯誤
看起來所有的都按照期望的那樣運行了,但是還有些情況沒有覆蓋到。到目前為止,我們使用小文件測試程序,當程序接收到文件創建事件后會打開文件,然后把文件拷貝到指定目錄中並關閉文件。如果我們接收一個大文件會出現什么情況?結果會是當線程嘗試訪問XML文件並填充DataSet 對象時,它會接收到一個嘗試打開正在使用的文件異常。現在我們拷貝huge_authors.xml 文件來再測試一遍程序。由於我們使用了跟蹤功能,你可以在日志文件中找到以下錯誤:
2012/4/28 13:53:14- Found: Authors.xml file
2012/4/28 13:53:14- Filling the DataSet
2012/4/28 13:53:14- Reading XML file
2012/4/28 13:53:22- A general exception occured during file processing:
2012/4/28 13:53:22- System.IO.IOException:
The process cannot access the file 'D:\Temp\Authors.xml' because it is being used by another process.
對這種類型的錯誤調試器通常捕獲不到,因為用來運行程序的時間和調試到代碼的時間足夠用來拷貝一個文件。在你的環境中也不一定發生,發生這個問題關鍵在於你的硬盤訪問速度以及內存大小。
在我們調用ReadXml() 方法之前,你應該嘗試顯示打開文件。如果發生了一個錯誤,那么你可以阻塞線程幾秒,然后在文件可以處理時再嘗試一次。讓我們看看DataImport2 是如何實現的,添加了一個GetFileAccess() 方法:
private bool GetFileAccess() { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Trying to get exclusive access to the " + mStrFileName + " file."); FileStream fs = null; try { fs = new FileStream(mStrFileName, FileMode.Append, FileAccess.Write, FileShare.None); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Access to the " + mStrFileName + " file allowed."); return true; } catch { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Access denied to the " + mStrFileName + " file."); return false; } finally { if (fs != null) { fs.Close(); } } }
GetFileAccess()方法會返回一個布爾值來指示是否可以訪問指定文件。這個方法將共享訪問屬性設置為空並嘗試打開文件:
public void Import() { string connectionStr = @"Data Source=.\SQLEXPRESS;Initial Catalog=pubs;Integrated Security=True"; SqlConnection dbConn = new SqlConnection(connectionStr); SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM authors", dbConn); DataSet ds = new DataSet(); SqlCommandBuilder sa = new SqlCommandBuilder(da); try { while (!GetFileAccess()) { Thread.Sleep(5000); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Trying to access to the " + mStrFileName + " file again."); } Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Filling the DataSet"); da.Fill(ds); DataSet dsMerge = new DataSet(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Reading XML file"); Thread.Sleep(8000); dsMerge.ReadXml(mStrFileName, XmlReadMode.InferSchema); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- DatSet filled with data."); DateTime time = DateTime.Now; Trace.WriteLineIf(DataImport.bs.Enabled, time + "- Updating database..."); da.TableMappings.Add("Table", "authors"); da.InsertCommand = sa.GetInsertCommand(); da.Update(dsMerge); DateTime time2 = DateTime.Now; Trace.WriteLineIf(DataImport.bs.Enabled, time + "- Database updated successfully."); Trace.Indent(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Total TIME: " + time2.Subtract(time) + " seconds"); Trace.Unindent(); } catch (SqlException ex) { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- A SQL exception occured during file processing: "); Trace.Indent(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- " + ex.ToString()); Trace.Unindent(); } catch (Exception e) { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- A general exception occured during file processing: "); Trace.Indent(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- " + e.ToString()); Trace.Unindent(); } finally { Trace.Flush(); } }
ImportData.Import() 方法顯式訪問文件。如果文件仍然被拷貝任務使用,線程會阻塞5秒。直到源文件可以被打開時才可以調用GetFileAccess() 方法。
到目前為止我們已經通過實用例子了解了跟蹤功能對在程序運行階段了解其行為是很重要的。
總結
在這一章我們已經了解如何使用Visual Studio.NET 調試器來觀察一個程序在執行過程中的行為。同時我們也學習使用調試器提供的允許我們檢查並改變一個變量值的強大工具,等等。
在本章的第二部分,我們使用.NET 提供的三個類來講解跟蹤功能:Trace, Debug 和 Switch. 我們通過在應用程序配置文件中修改值來動態地激活/反激活跟蹤功能。
最后,我們通過一個實例學習了幫助開發人員找到並修改問題和邏輯錯誤的跟蹤技術。
下一篇介紹 第七章 網絡和線程…