在文件解锁并且可以读取和重命名之前,阻塞线程的最简单方法是什么?例如,.NET Framework中的某个地方是否存在WaitOnFile()?
我有一个服务,使用FileSystemWatcher查找要传输到FTP站点的文件,但文件创建事件在另一个进程写完文件之前触发.
理想的解决方案将有一个超时期限,因此线程在放弃之前不会永久挂起.
编辑:在尝试下面的一些解决方案后,我最终更改系统,以便所有文件写入Path.GetTempFileName()
,然后执行File.Move()
到最终位置.一旦FileSystemWatcher
事件被触发,该文件就已经完成.
从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; }
这是我在一个相关问题上给出的答案:
////// Blocks until the file is not locked any more. /// /// 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; }
这是执行此操作的通用代码,与文件操作本身无关.这是一个如何使用它的示例:
WrapSharingViolations(() => File.Delete(myFile));
要么
WrapSharingViolations(() => File.Copy(mySourceFile, myDestFile));
您还可以定义重试计数以及重试之间的等待时间.
注意:不幸的是,底层的Win32错误(ERROR_SHARING_VIOLATION)没有暴露在.NET中,所以我添加了一个IsSharingViolation
基于反射机制的小hack函数()来检查这一点.
////// Wraps sharing violations that could occur on a file IO operation. /// /// The action to execute. May not be null. public static void WrapSharingViolations(WrapSharingViolationsCallback action) { WrapSharingViolations(action, null, 10, 100); } ////// Wraps sharing violations that could occur on a file IO operation. /// /// The action to execute. May not be null. /// The exceptions callback. May be null. /// The retry count. /// The wait time in milliseconds. 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; } } } } ////// Defines a sharing violation wrapper delegate. /// public delegate void WrapSharingViolationsCallback(); ////// Defines a sharing violation wrapper delegate for handling exception. /// public delegate bool WrapSharingViolationsExceptionsCallback(IOException ioe, int retry, int retryCount, int waitTime); ////// Determines whether the specified exception is a sharing violation exception. /// /// The exception. May not be null. ////// 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 } ///true if the specified exception is a sharing violation exception; otherwise,false . ////// Gets the HRESULT of the specified exception. /// /// The exception to test. May not be null. /// The default value in case of an error. ///The HRESULT value. 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; }
我为这些事情拼凑了一个助手类.如果您可以控制访问该文件的所有内容,它将起作用.如果你期待其他一些事情的争论,那么这是毫无价值的.
using System; using System.IO; using System.Threading; ////// 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). /// 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 ////// Opens the stream when it is not locked. If the file is locked, then /// 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.这应该最有可能在使用块内部使用,以确保正确释放锁定,并且在放置此对象时将正确处理流(如果打开).
我做了一个快速的测试,大约有20件事要对文件进行各种读/写操作,并且没有看到任何损坏.显然它不是很先进,但它应该适用于大多数简单的情况.
对于这个特定的应用程序,直接观察文件将不可避免地导致难以追踪的错误,特别是当文件大小增加时.以下是两种不同的策略.
Ftp两个文件,但只看一个.例如,发送文件important.txt和important.finish.只监视完成文件但处理txt.
FTP一个文件,但完成后重命名.例如,发送important.wait并让发件人在完成后将其重命名为important.txt.
祝好运!