等到文件解锁在.NET中

阻塞线程最简单的方法是什么,直到一个文件已被解锁,并可读取和重命名? 例如,.NET Framework中是否有WaitOnFile()?

我有一个使用FileSystemWatcher的服务来查找要传输到FTP站点的文件 ,但是文件创build的事件在另一个进程完成写入文件之前触发。

理想的解决scheme会有一个超时期限,所以线程在放弃之前不会永远挂起。

编辑:在尝试了下面的一些解决scheme后,我结束了改变系统 ,所有文件写入到Path.GetTempFileName() ,然后执行一个File.Move()到最后的位置。 一旦FileSystemWatcher事件触发,文件已经完成。

这是我在一个相关的问题上给出的答案:

  /// <summary> /// Blocks until the file is not locked any more. /// </summary> /// <param name="fullPath"></param> bool WaitForFile(string fullPath) { int numTries = 0; while (true) { ++numTries; try { // Attempt to open the file exclusively. using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 100)) { fs.ReadByte(); // If we got this far the file is ready break; } } catch (Exception ex) { Log.LogWarning( "WaitForFile {0} failed to get an exclusive lock: {1}", fullPath, ex.ToString()); if (numTries > 10) { Log.LogWarning( "WaitForFile {0} giving up after 10 tries", fullPath); return false; } // Wait for the lock to be released System.Threading.Thread.Sleep(500); } } Log.LogTrace("WaitForFile {0} returning true after {1} tries", fullPath, numTries); return true; } 

从Eric的回答开始,我包含了一些改进,使代码更加紧凑和可重用。 希望它是有用的。

 FileStream WaitForFile (string fullPath, FileMode mode, FileAccess access, FileShare share) { for (int numTries = 0; numTries < 10; numTries++) { FileStream fs = null; try { fs = new FileStream (fullPath, mode, access, share); return fs; } catch (IOException) { if (fs != null) { fs.Dispose (); } Thread.Sleep (50); } } return null; } 

这是一个通用的代码来做到这一点,独立于文件操作本身。 这是一个如何使用它的例子:

 WrapSharingViolations(() => File.Delete(myFile)); 

要么

 WrapSharingViolations(() => File.Copy(mySourceFile, myDestFile)); 

您还可以定义重试计数以及重试之间的等待时间。

注意:不幸的是,底层的Win32错误(ERROR_SHARING_VIOLATION)没有公开.NET,所以我添加了一个基于reflection机制的小黑客函数( IsSharingViolation )来检查这个。

  /// <summary> /// Wraps sharing violations that could occur on a file IO operation. /// </summary> /// <param name="action">The action to execute. May not be null.</param> public static void WrapSharingViolations(WrapSharingViolationsCallback action) { WrapSharingViolations(action, null, 10, 100); } /// <summary> /// Wraps sharing violations that could occur on a file IO operation. /// </summary> /// <param name="action">The action to execute. May not be null.</param> /// <param name="exceptionsCallback">The exceptions callback. May be null.</param> /// <param name="retryCount">The retry count.</param> /// <param name="waitTime">The wait time in milliseconds.</param> public static void WrapSharingViolations(WrapSharingViolationsCallback action, WrapSharingViolationsExceptionsCallback exceptionsCallback, int retryCount, int waitTime) { if (action == null) throw new ArgumentNullException("action"); for (int i = 0; i < retryCount; i++) { try { action(); return; } catch (IOException ioe) { if ((IsSharingViolation(ioe)) && (i < (retryCount - 1))) { bool wait = true; if (exceptionsCallback != null) { wait = exceptionsCallback(ioe, i, retryCount, waitTime); } if (wait) { System.Threading.Thread.Sleep(waitTime); } } else { throw; } } } } /// <summary> /// Defines a sharing violation wrapper delegate. /// </summary> public delegate void WrapSharingViolationsCallback(); /// <summary> /// Defines a sharing violation wrapper delegate for handling exception. /// </summary> public delegate bool WrapSharingViolationsExceptionsCallback(IOException ioe, int retry, int retryCount, int waitTime); /// <summary> /// Determines whether the specified exception is a sharing violation exception. /// </summary> /// <param name="exception">The exception. May not be null.</param> /// <returns> /// <c>true</c> if the specified exception is a sharing violation exception; otherwise, <c>false</c>. /// </returns> public static bool IsSharingViolation(IOException exception) { if (exception == null) throw new ArgumentNullException("exception"); int hr = GetHResult(exception, 0); return (hr == -2147024864); // 0x80070020 ERROR_SHARING_VIOLATION } /// <summary> /// Gets the HRESULT of the specified exception. /// </summary> /// <param name="exception">The exception to test. May not be null.</param> /// <param name="defaultValue">The default value in case of an error.</param> /// <returns>The HRESULT value.</returns> public static int GetHResult(IOException exception, int defaultValue) { if (exception == null) throw new ArgumentNullException("exception"); try { const string name = "HResult"; PropertyInfo pi = exception.GetType().GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance); // CLR2 if (pi == null) { pi = exception.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance); // CLR4 } if (pi != null) return (int)pi.GetValue(exception, null); } catch { } return defaultValue; } 

我为这类事情扔了一个助手class。 如果你能够控制所有可以访问文件的东西,它就会工作。 如果你期望从其他一些事情的争夺,那么这是非常没有价值的。

 using System; using System.IO; using System.Threading; /// <summary> /// This is a wrapper aroung a FileStream. While it is not a Stream itself, it can be cast to /// one (keep in mind that this might throw an exception). /// </summary> public class SafeFileStream: IDisposable { #region Private Members private Mutex m_mutex; private Stream m_stream; private string m_path; private FileMode m_fileMode; private FileAccess m_fileAccess; private FileShare m_fileShare; #endregion//Private Members #region Constructors public SafeFileStream(string path, FileMode mode, FileAccess access, FileShare share) { m_mutex = new Mutex(false, String.Format("Global\\{0}", path.Replace('\\', '/'))); m_path = path; m_fileMode = mode; m_fileAccess = access; m_fileShare = share; } #endregion//Constructors #region Properties public Stream UnderlyingStream { get { if (!IsOpen) throw new InvalidOperationException("The underlying stream does not exist - try opening this stream."); return m_stream; } } public bool IsOpen { get { return m_stream != null; } } #endregion//Properties #region Functions /// <summary> /// Opens the stream when it is not locked. If the file is locked, then /// </summary> public void Open() { if (m_stream != null) throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage); m_mutex.WaitOne(); m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare); } public bool TryOpen(TimeSpan span) { if (m_stream != null) throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage); if (m_mutex.WaitOne(span)) { m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare); return true; } else return false; } public void Close() { if (m_stream != null) { m_stream.Close(); m_stream = null; m_mutex.ReleaseMutex(); } } public void Dispose() { Close(); GC.SuppressFinalize(this); } public static explicit operator Stream(SafeFileStream sfs) { return sfs.UnderlyingStream; } #endregion//Functions } 

它使用一个有名的互斥体。 那些希望访问该文件的人试图获得对指定互斥体的控制,这个互斥体共享文件的名字('\'变成'/')。 您可以使用Open(),它会停止,直到可以访问互斥锁,或者您可以使用TryOpen(TimeSpan),它尝试获取给定持续时间的互斥锁,如果在时间跨度内无法获取,则返回false。 这应该最有可能在一个使用块内部使用,以确保锁被正确释放,并且当该对象被丢弃时stream(如果打开)将被正确地处置。

我做了一个快速testing〜20个事情做文件的各种读/写,没有看到腐败。 显然这不是很先进,但它应该适用于大多数简单的情况。

对于这个特定的应用程序,直接观察文件将不可避免地导致很难追踪错误,特别是当文件大小增加时。 以下是两种不同的策略。

  • Ftp两个文件,但只看一个。 例如发送文件important.txt和important.finish。 只注意完成文件,但处理TXT。
  • FTP一个文件,但完成后重命名。 例如发送important.wait并在发送完成后让发送者将其重命名为important.txt。

祝你好运!

我之前用过的技术之一是编写我自己的function。 基本上捕捉exception,并使用一个可以在指定的时间内触发的计时器重试。 如果有更好的方法,请分享。

来自MSDN :

一旦创build文件,就会引发OnCreated事件。 如果正在将文件复制或传输到监视目录中,则会立即引发OnCreated事件,然后是一个或多个OnChanged事件。

您的FileSystemWatcher可以被修改,以便它不会在“OnCreated”事件中进行读/重命名,而是:

  1. 跨越一个线程轮询文件状态,直到它没有被locking(使用FileInfo对象)
  2. 一旦确定文件不再被locking并准备好,立即调用该服务来处理该文件

在大多数情况下,像@harpobuild议的简单方法将起作用。 您可以使用这种方法开发更复杂的代码:

  • 使用SystemHandleInformation \ SystemProcessInformation查找选定文件的所有打开的句柄
  • 子类WaitHandle类访问它的内部句柄
  • 通过发现的句柄包装在WaitHandle的子类WaitHandle.WaitAny方法中

广告传输文件传输完成后创build的进程触发文件SameNameASTrasferedFile.trg。

然后设置FileSystemWatcher,它只会在* .trg文件中触发事件。

我不知道你用什么来确定文件的locking状态,但是这样的事情应该这样做。

而(真)
 {
    尝试{
         stream = File.Open(fileName,fileMode);
        打破;
     }
     catch(FileIOException){

         //检查是否是locking问题

         Thread.Sleep(100);
     }
 }

一个可能的解决scheme是,将文件系统监视器与一些轮询结合起来,

得到通知每一个文件的变化,当得到通知检查是否被locking,如目前接受的答案中所述: https ://stackoverflow.com/a/50800/6754146打开文件stream的代码是从答案复制并稍作修改:

 public static void CheckFileLock(string directory, string filename, Func<Task> callBack) { var watcher = new FileSystemWatcher(directory, filename); FileSystemEventHandler check = async (sender, eArgs) => { string fullPath = Path.Combine(directory, filename); try { // Attempt to open the file exclusively. using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 100)) { fs.ReadByte(); watcher.EnableRaisingEvents = false; // If we got this far the file is ready } watcher.Dispose(); await callBack(); } catch (IOException) { } }; watcher.NotifyFilter = NotifyFilters.LastWrite; watcher.IncludeSubdirectories = false; watcher.EnableRaisingEvents = true; //Attach the checking to the changed method, //on every change it gets checked once watcher.Changed += check; //Initially do a check for the case it is already released check(null, null); } 

通过这种方式,您可以检查文件是否locking,并在指定的callbackclosures时收到通知,这样可以避免过度激进的轮询,只有在实际可能closures的情况下才会执行该操作

我这样做就像古尔扎尔,只是不断尝试一个循环。

实际上,我甚至不用打扰文件系统观察者。 轮询networking驱动器的新文件一分钟便宜。

只需使用带有NotifyFilter NotifyFilters.LastWriteChanged事件:

 var watcher = new FileSystemWatcher { Path = @"c:\temp\test", Filter = "*.xml", NotifyFilter = NotifyFilters.LastWrite }; watcher.Changed += watcher_Changed; watcher.EnableRaisingEvents = true; 

添加Outlook附件时遇到类似的问题。 “使用”保存了一天。

 string fileName = MessagingBLL.BuildPropertyAttachmentFileName(currProp); //create a temporary file to send as the attachment string pathString = Path.Combine(Path.GetTempPath(), fileName); //dirty trick to make sure locks are released on the file. using (System.IO.File.Create(pathString)) { } mailItem.Subject = MessagingBLL.PropertyAttachmentSubject; mailItem.Attachments.Add(pathString, Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing); 

如何作为一个选项:

 private void WaitOnFile(string fileName) { FileInfo fileInfo = new FileInfo(fileName); for (long size = -1; size != fileInfo.Length; fileInfo.Refresh()) { size = fileInfo.Length; System.Threading.Thread.Sleep(1000); } } 

当然,如果文件大小在创build时被预分配,你会得到一个误报。