2017-10-10 190 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;