C# 文件系统监视程序丢失文件
我是c#的新手,正在编写一个程序,使用从folderWatch方法调用的fileSystemWatcher来监视文件夹中的.xml文件。xml文件包含一个电子邮件地址和一个指向图像的路径,一旦读取就会通过电子邮件发送。如果我一次只添加几个xml,那么我的代码可以很好地工作,但是当我试图将大量xml转储到文件夹fileSystemWatcher中时,并没有处理所有xml。请帮我指出正确的方向C# 文件系统监视程序丢失文件,c#,xml,filesystemwatcher,C#,Xml,Filesystemwatcher,我是c#的新手,正在编写一个程序,使用从folderWatch方法调用的fileSystemWatcher来监视文件夹中的.xml文件。xml文件包含一个电子邮件地址和一个指向图像的路径,一旦读取就会通过电子邮件发送。如果我一次只添加几个xml,那么我的代码可以很好地工作,但是当我试图将大量xml转储到文件夹fileSystemWatcher中时,并没有处理所有xml。请帮我指出正确的方向 private System.IO.FileSystemWatcher m_Watcher; public
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);
}
}
首先,
FileSystemWatcher
对存储挂起的通知进行了内部限制。根据文件:
系统通知组件文件更改,并存储这些更改
组件创建并传递给API的缓冲区中的更改。每个
事件最多可使用16个字节的内存,不包括文件名。
如果在短时间内有许多更改,缓冲区可能会溢出。
这会导致组件无法跟踪目录中的更改
您可以通过将InternalBufferSize
设置为64*1024
(64KB,最大允许值)来增加该缓冲区
接下来(也许更重要)是如何清除缓冲区。您的OnChanged
处理程序将被调用,并且仅当它完成时-通知将从该缓冲区中删除。这意味着,如果您在处理程序中做了大量工作,缓冲区溢出的可能性会大得多。为了避免这种情况,请在OnChanged
处理程序中尽可能少做一些工作,并在单独的线程中完成所有繁重的工作,例如(不是生产准备好的代码,只是为了错觉):
var queue=new BlockingCollection(new ConcurrentQueue());
新线程(()=>{
foreach(队列中的变量项。GetConsumingEnumerable()){
//用物品做重物
}
}) {
IsBackground=true
}.Start();
var w=new FileSystemWatcher();
//其他东西
w、 已更改+=(发件人,参数)=>
{
//不需要花费时间,因此溢出机会大大减少
添加(args.FullPath);
};
您也没有订阅
FileSystemWatcher
的Error
事件,因此您不知道何时(以及是否)出现问题。我学到了一个艰难的方法,如果您必须使用可靠的文件监视器,请使用
如果您有足够的权限,可以通过以下方式访问.NET:
您也可以使用flie Length+LastModifiedDate通过计时器轮询手动实现此功能。FSW的文档警告,如果事件处理时间过长,某些事件可能会丢失。这就是为什么它总是与队列和/或后台处理一起使用 一个选项是使用Task.Run在后台执行处理:
public void OnChanged(object sender, FileSystemEventArgs e)
{
_logger.Info("File Added " + e.FullPath);
Task.Run(()=>xmlRead(e.FullPath));
}
请注意,我使用日志记录而不是displayText
所做的任何事情。无法从其他线程访问UI线程。如果要记录进度,请使用日志库
您还可以使用IProgress
界面来报告长时间运行的作业的进度,或者您希望通过它发布的任何其他内容。Progress
实现负责将Progress对象封送到它的父线程,通常是UI线程
更好的解决方案是使用。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);
}
和sendmail
发送至:
public void sendMailFromInfo(EmailInfo info)
{
string email=info.Data;
string attachmentFilename=info.Attachment;
}
当您想要终止管道时,在head块上调用Complete()
,并等待尾部的完成。这将确保处理所有剩余文件:
readerBlock.Complete();
await mailerBlock.Completion;
它很可能会因为花了大量时间来编写代码而丢失这些代码-将其线程化并有一个文件队列您必须使用错误事件让FSW告诉您您做错了。FSW非常容易出错。由于某些文件系统事件,它将随机停止侦听(没有任何错误通信)。如果有兴趣的话,我有一个可以让它更容易可靠地使用的工具。修改日期本身是不可靠和缓慢的。如果你不滥用FSW,它就可以正常工作。并且.NET无法访问日志,除非您使用AlphaFS这样的库并拥有管理员权限以便在整个卷中启用它。您还没有意识到,使用轮询更轻松,使用上次修改的日期+长度就足以知道在大多数情况下文件是否已更改。如果需要超高精度,可以使用文件流的前几位或最后几位的md5哈希。你只需要知道如何阅读日记。不需要一些巨大的库。谢谢Evk经过一点努力,我让它工作起来了,似乎文件不再丢失了。@Evk我有一个类似的情况,我的代码中的更改看起来像
w.Changed+=(sender,args)=>{//这里我调用了一个函数,它包含4个参数,性能(string,string,string,int);//那么有没有一种方法可以存储所有4个参数,这样我就可以从单独的thread.queue.Add(args.FullPath);}
@m_alpha您可以创建具有4个属性(PerformAction的参数)的单独类,并将该类的实例存储在队列中,而不仅仅是一个字符串。@Evk感谢您的回答,我昨天也做了类似的事情。创建了一个结构,然后将其实例存储在队列中而不是类中。想询问是否可以在事件处理程序本身中调用新线程(()=>{foreach(queue.getconsumineGenumerable()中的var item){//使用item}
,即w.Changed+=(发送方,参数)
@m_alpha在这种情况下,您将在每次更改时创建一个新线程,因此,如果有100个更改,则将有100个线程执行相同的操作。您应该在处理程序之外执行此操作。
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};
}
...
}
}
public void sendMailFromInfo(EmailInfo info)
{
string email=info.Data;
string attachmentFilename=info.Attachment;
}
readerBlock.Complete();
await mailerBlock.Completion;