等待文件在.NET中解锁
在文件被解锁并可以读取和重命名之前,阻塞线程的最简单方法是什么?例如,.NET Framework中是否存在WaitOnFile()?
我有一个使用FileSystemWatcher来查找要传输到FTP站点的文件的服务,但是在其他进程完成写入文件之前触发了文件创建事件。
理想的解决方案将具有超时期限,因此线程不会在放弃之前永远挂起。
编辑:尝试了下面的一些解决方案后,我最终更改了系统,以便所有文件都写入Path.GetTempFileName()
,然后对最终位置执行File.Move()
。一旦FileSystemWatcher事件触发,文件已经完成。
解决方案
回答
我前一段时间使用的一种技术是编写自己的函数。基本上捕获异常,然后使用计时器重试,我们可以在指定的时间段内触发该计时器。如果有更好的方法,请分享。
回答
我不知道我们要使用什么来确定文件的锁定状态,但是应该这样做。
while (true) { try { stream = File.Open( fileName, fileMode ); break; } catch( FileIOException ) { // check whether it's a lock problem Thread.Sleep( 100 ); } }
回答
从MSDN:
The OnCreated event is raised as soon as a file is created. If a file is being copied or transferred into a watched directory, the OnCreated event will be raised immediately, followed by one or more OnChanged events.
我们可以修改FileSystemWatcher,以便在" OnCreated"事件期间不进行读取/重命名,而是:
- 生成一个轮询文件状态的线程,直到它未被锁定为止(使用FileInfo对象)
- 一旦确定文件不再被锁定并准备就绪,则回调服务以处理该文件
回答
这是我对一个相关问题的回答:
/// <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; }
回答
我做的方式与Gulzar相同,只是不断尝试循环。
实际上,我什至不理会文件系统监视程序。每分钟轮询一次网络驱动器以获取新文件很便宜。
回答
在大多数情况下,建议使用@harpo这样的简单方法。我们可以使用这种方法开发更复杂的代码:
- 使用SystemHandleInformation \ SystemProcessInformation查找所选文件的所有打开的句柄
- 子类WaitHandle类获得对其内部句柄的访问
- 将包装在子类WaitHandle中的找到的句柄传递给WaitHandle.WaitAny方法
回答
对于此特定应用程序,直接观察文件将不可避免地导致难以跟踪的错误,尤其是当文件大小增加时。这是两种可行的策略。
- ftp两个文件,但只能观看一个。例如,发送文件Important.txt和Important.finish。仅注意整理文件,但处理txt。
- FTP一个文件,但完成后将其重命名。例如,发送Important.wait,并在完成后让发送方将其重命名为Important.txt。
祝你好运!
回答
如何作为一个选择:
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); } }
当然,如果文件大小在创建时已预先分配,我们将得到误报。
回答
我为这类事情召集了一个帮助班。如果我们可以控制将访问该文件的所有内容,它将可以正常工作。如果我们期望其他方面的竞争,那么这毫无用处。
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)。这应该最有可能在using块内使用,以确保正确释放锁,并且在处置此对象时将正确处置流(如果已打开)。
我用大约20件事进行了快速测试,以对文件进行各种读/写操作,但未发现损坏。显然,它不是很高级,但是它应该适用于大多数简单情况。
回答
从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; }