2017-10-10 193 views
3

我是新的c#,並正在編寫一個程序,該程序將使用名爲folderWatch的方法調用fileSystemWatcher來監視.xml文件的文件夾。 .xml文件包含一個電子郵件地址和一個圖像的路徑,一旦讀取就會通過電子郵件發送。我的代碼工作正常,如果我一次只添加幾個XML,但是當我試圖將大量數據轉儲到文件夾fileSystemWatcher不處理所有這些文件時。請幫助我指出正確的方向。FileSystemWatcher丟失文件

private System.IO.FileSystemWatcher m_Watcher; 
public string folderMonitorPath = Properties.Settings.Default.monitorFolder; 

    public void folderWatch() 
    { 
     if(folderMonitorPath != "") 
     { 
      m_Watcher = new System.IO.FileSystemWatcher(); 
      m_Watcher.Filter = "*.xml*"; 
      m_Watcher.Path = folderMonitorPath; 
      m_Watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite 
            | NotifyFilters.FileName | NotifyFilters.DirectoryName; 
      m_Watcher.Created += new FileSystemEventHandler(OnChanged); 
      m_Watcher.EnableRaisingEvents = true; 
     } 
    } 

    public void OnChanged(object sender, FileSystemEventArgs e) 
    { 
     displayText("File Added " + e.FullPath); 
     xmlRead(e.FullPath); 
    } 

讀取XML

public void xmlRead(string path) 
    { 

     XDocument document = XDocument.Load(path); 
     var photo_information = from r in document.Descendants("photo_information") 
           select new 
           { 
            user_data = r.Element("user_data").Value, 
            photos = r.Element("photos").Element("photo").Value, 
           }; 
     foreach (var r in photo_information) 
     { 
      if (r.user_data != "") 
      { 
       var attachmentFilename = folderMonitorPath + @"\" + r.photos; 
       displayText("new user data " + r.user_data); 
       displayText("attemting to send mail"); 
       sendemail(r.user_data, attachmentFilename); 
      } 
      else 
      { 
       displayText("no user data moving to next file"); 
      } 
     } 

發送郵件

public void sendemail(string email, string attachmentFilename) 
    { 
     //myTimer.Stop(); 

      MailMessage mail = new MailMessage(); 
      SmtpClient SmtpServer = new SmtpClient(smtpClient); 

      mail.From = new MailAddress(mailFrom); 
      mail.To.Add(email); 
      mail.Subject = "test"; 
      mail.Body = "text"; 

      SmtpServer.Port = smtpPort; 
     SmtpServer.Credentials = new System.Net.NetworkCredential("username", "password"); 
     SmtpServer.EnableSsl = true; 
     // SmtpServer.UseDefaultCredentials = true; 

     if (attachmentFilename != null) 
      { 
       Attachment attachment = new Attachment(attachmentFilename, MediaTypeNames.Application.Octet); 
       ContentDisposition disposition = attachment.ContentDisposition; 
       disposition.CreationDate = File.GetCreationTime(attachmentFilename); 
       disposition.ModificationDate = File.GetLastWriteTime(attachmentFilename); 
       disposition.ReadDate = File.GetLastAccessTime(attachmentFilename); 
       disposition.FileName = Path.GetFileName(attachmentFilename); 
       disposition.Size = new FileInfo(attachmentFilename).Length; 
       disposition.DispositionType = DispositionTypeNames.Attachment; 
       mail.Attachments.Add(attachment); 
      } 
     try 
     { 
      SmtpServer.Send(mail); 
      displayText("mail sent"); 
     } 
     catch (Exception ex) 
     { 
      displayText(ex.Message); 

     } 

    } 
+2

機會是其缺少他們,因爲花費的時間做所有的代碼 - 它擰斷,並有文件的隊列 – BugFinder

+0

您必須使用錯誤事件得到FSW告訴你,你做錯了。 –

+0

FSW非常容易出錯。由於某些文件系統事件,它會隨機停止監聽 - 沒有任何錯誤傳送。如果有興趣,我有一個[可觀察FileSystemWatcher的(http://idcomlog.codeplex.com/SourceControl/latest#IdComLog.Reactive/FileSystem.cs),使得它更容易可靠地使用。 –

回答

1

首先,FileSystemWatcher有內部有限buffer儲存待處理通知。按照文檔:

系統通知的文件的改變的部件,並且其存儲在緩衝器中的組件創建和傳遞給API的那些 變化。每個 事件最多可以使用16個字節的內存,不包括文件名。 如果在短時間內有很多更改,緩衝區可能會溢出。 這將導致組件失去對目錄

跟蹤變化可以通過設置InternalBufferSize64 * 1024增加緩存(64KB,最大允許值)。

接下來(也許更重要的)是如何清除這個緩衝區。你的OnChanged處理程序被調用,只有當它完成 - 通知從該緩衝區中刪除。這意味着如果你在處理程序中做了很多工作 - 緩衝區溢出的可能性要大得多。爲了避免這種情況 - 在OnChanged處理程序做的一點工作儘可能做在單獨的線程中的所有繁重的工作,例如(未投入生產的代碼,只是爲了illustation目的):

var queue = new BlockingCollection<string>(new ConcurrentQueue<string>()); 
new Thread(() => { 
    foreach (var item in queue.GetConsumingEnumerable()) { 
     // do heavy stuff with item 
    } 
}) { 
    IsBackground = true 
}.Start(); 
var w = new FileSystemWatcher(); 
// other stuff 
w.Changed += (sender, args) => 
{ 
    // takes no time, so overflow chance is drastically reduced 
    queue.Add(args.FullPath); 
}; 

您還沒有訂閱ErrorFileSystemWatcher事件,所以你不知道什麼時候(和如果)出現問題。

+0

謝謝EVK一點tweeking後,我得到它的工作,似乎沒有文件得到了丟失。 – user3260707

-1

我學到了艱辛的道路,如果你必須使用一個可靠的文件監控,使用USN Journals

https://msdn.microsoft.com/en-us/library/windows/desktop/aa363798(v=vs.85).aspx

這裏是你可以訪問.NET的路要走,如果你有足夠的權限:https://stackoverflow.com/a/31931109/612717

你也可以使用該flie長度+ LastModifiedDate定時輪詢實現它自己手動。

+1

修改日期本身不可靠,* *速度慢。如果你不使用它,FSW將工作得很好。和.NET無法訪問日誌,除非你使用圖書館像AlphaFS *和*擁有管理權限,以使其能夠爲整個volumne –

+0

,哪些是你還沒有意識到的是,它更輕使用輪詢,它是足以使用上次修改日期+長度來了解文件在大多數情況下是否發生了更改。如果需要超高精度,則可以使用文件流的前幾位或後幾位的md5散列。你只需要知道如何閱讀日記。無需一些大型圖書館。 –

0

FSW的文件警告說,如果事件處理時間過長,一些事件可能會丟失。這就是爲什麼它總是用於隊列和/或後臺處理。

一種選擇是使用Task.Run在後臺進行處理:

public void OnChanged(object sender, FileSystemEventArgs e) 
{ 
    _logger.Info("File Added " + e.FullPath); 
    Task.Run(()=>xmlRead(e.FullPath)); 
} 

請注意,我用日誌記錄,而不是什麼displayText做。您不能從另一個線程訪問UI線程。如果您想記錄進度,請使用記錄庫。

您還可以使用IProgress< T>界面報告長時間運行的工作進度,或者其他任何你想通過它來發布。該Progress< T>實施照顧到進度對象元帥之父線程,通常UI線程。

甚至更好解決方案是使用ActionBlock< T>。一個ActionBlock有一個輸入緩衝區,可以對輸入的消息進行排隊,還有一個DOP設置,允許你指定可以同時執行多少個操作。默認值是1:

ActionBlock<string> _mailerBlock; 

public void Init() 
{ 
    var options=new ExecutionDataflowBlockOptions { 
     MaxDegreeOfParallelism = 5 
    }; 
    _mailerBlock = new ActionBlock<string>(path=>xlmRead(path),options); 
} 

public void OnChanged(object sender, FileSystemEventArgs e) 
{ 
    _logger.Info("File Added " + e.FullPath); 
    _mailerBlock.Post(e.FullPath); 
} 

更重要的是,你可以閱讀和發送電子郵件創建不同的充塊,並在管道連接。在這種情況下,文件讀出器產生大量的電子郵件,這意味着TransformManyBlock需要:

class EmailInfo 
{ 
    public string Data{get;set;} 
    public string Attachement{get;set;} 
} 


var readerBlock = new TransformManyBlock<string,EmailInfo>(path=>infosFromXml(path)); 

var mailBlock = new ActionBlock<EmailInfo>(info=>sendMailFromInfo(info)); 

readerBlock.LinkTo(mailBlock,new DataflowLinkOptions{PropagateCompletion=true}); 

xmlRead方法應被變更爲一個迭代

public IEnumerable<EmailInfo> infosFromXml(string path) 
{ 
    // Same as before ... 
    foreach (var r in photo_information) 
    { 
     if (r.user_data != "") 
     { 
      ... 
      yield return new EmailInfo{ 
         Data=r.user_data, 
         Attachment=attachmentFilename}; 
     } 
     ... 
    } 
} 

而且sendmail到:

public void sendMailFromInfo(EmailInfo info) 
{ 
    string email=info.Data; 
    string attachmentFilename=info.Attachment; 
} 

當你想終止你的頭塊上調用Complete()指日可待尾部的completi管道上。這確保了所有剩餘的文件將被處理:

readerBlock.Complete(); 
await mailerBlock.Completion;