等到文件在 .NET 中解锁
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/50744/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Wait until file is unlocked in .NET
提问by Chris Wenham
What's the simplest way of blocking a thread until a file has been unlocked and is accessible for reading and renaming? For example, is there a WaitOnFile() somewhere in the .NET Framework?
在文件被解锁并且可以读取和重命名之前阻塞线程的最简单方法是什么?例如,.NET Framework 中是否有 WaitOnFile() ?
I have a service that uses a FileSystemWatcher to look for files that are to be transmitted to an FTP site, but the file createdevent fires before the other process has finished writing the file.
我有一个服务,它使用 FileSystemWatcher 来查找要传输到 FTP 站点的文件,但文件创建事件在其他进程完成写入文件之前触发。
The ideal solution would have a timeout period so the thread doesn't hang forever before giving up.
理想的解决方案应该有一个超时期限,这样线程在放弃之前不会永远挂起。
Edit: After trying out some of the solutions below, I ended up changing the systemso that all files wrote to Path.GetTempFileName(), then performed a File.Move()to the final location. As soon as the FileSystemWatcherevent fired, the file was already complete.
编辑:在尝试了下面的一些解决方案后,我最终更改了系统,以便所有文件都写入Path.GetTempFileName(),然后执行File.Move()到最终位置。一旦FileSystemWatcher触发的事件,该文件已经完成。
采纳答案by Eric Z Beard
This was the answer I gave on a related question:
这是我在一个相关问题上给出的答案:
/// <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;
}
回答by mafu
Starting from Eric's answer, I included some improvements to make the code far more compact and reusable. Hope it's useful.
从 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;
}
回答by Simon Mourier
Here is a generic code to do this, independant from the file operation itself. This is an example on how to use it:
这是执行此操作的通用代码,独立于文件操作本身。这是有关如何使用它的示例:
WrapSharingViolations(() => File.Delete(myFile));
or
或者
WrapSharingViolations(() => File.Copy(mySourceFile, myDestFile));
You can also define the retry count, and the wait time between retries.
您还可以定义重试次数和重试之间的等待时间。
NOTE: Unfortunately, the underlying Win32 error (ERROR_SHARING_VIOLATION) is not exposed with .NET, so I have added a small hack function (IsSharingViolation) based on reflection mechanisms to check this.
注意:不幸的是,底层的 Win32 错误 (ERROR_SHARING_VIOLATION) 没有在 .NET 中公开,所以我添加了一个IsSharingViolation基于反射机制的小 hack 函数 ( ) 来检查这个。
/// <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;
}
回答by user152791
I threw together a helper class for these sorts of things. It will work if you have control over everything that would access the file. If you're expecting contention from a bunch of other things, then this is pretty worthless.
我为这些事情拼凑了一个助手类。如果您可以控制访问该文件的所有内容,它将起作用。如果您期望从一堆其他事物中进行争用,那么这将毫无价值。
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
}
It works using a named mutex. Those wishing to access the file attempt to acquire control of the named mutex, which shares the name of the file (with the '\'s turned into '/'s). You can either use Open(), which will stall until the mutex is accessible or you can use TryOpen(TimeSpan), which tries to acquire the mutex for the given duration and returns false if it cannot acquire within the time span. This should most likely be used inside a using block, to ensure that locks are released properly, and the stream (if open) will be properly disposed when this object is disposed.
它使用命名的互斥锁工作。那些希望访问该文件的人试图获得对命名互斥锁的控制,该互斥锁共享文件的名称('\'s 变成了'/'s)。您可以使用 Open(),它会在互斥锁可访问之前停止运行,或者您可以使用 TryOpen(TimeSpan),它会尝试在给定的持续时间内获取互斥锁,如果在该时间跨度内无法获取则返回 false。这最有可能在 using 块内使用,以确保正确释放锁,并且在释放此对象时将正确释放流(如果打开)。
I did a quick test with ~20 things to do various reads/writes of the file and saw no corruption. Obviously it's not very advanced, but it should work for the majority of simple cases.
我用大约 20 件事做了一个快速测试来对文件进行各种读/写,没有发现任何损坏。显然它不是很先进,但它应该适用于大多数简单情况。
回答by jason saldo
For this particular application directly observing the file will inevitably lead to a hard to trace bug, especially when the file size increases. Here are two different strategies that will work.
对于这个特定的应用程序,直接观察文件将不可避免地导致难以追踪的错误,尤其是当文件大小增加时。这是两种有效的不同策略。
- Ftp two files but only watch one. For example send the files important.txt and important.finish. Only watch for the finish file but process the txt.
- FTP one file but rename it when done. For example send important.wait and have the sender rename it to important.txt when finished.
- ftp 两个文件但只看一个。例如发送文件important.txt 和important.finish。只看完成文件,但处理 txt。
- FTP 一个文件,但完成后重命名。例如发送 important.wait 并在完成后让发件人将其重命名为 important.txt。
Good luck!
祝你好运!
回答by Gulzar Nazim
One of the techniques I used some time back was to write my own function. Basically catch the exception and retry using a timer which you can fire for a specified duration. If there is a better way, please share.
前段时间我使用的一种技术是编写自己的函数。基本上捕获异常并使用可以在指定持续时间内触发的计时器重试。如果有更好的方法,请分享。
回答by Guy Starbuck
From MSDN:
从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.
一旦创建文件,就会引发 OnCreated 事件。如果一个文件被复制或传输到一个被监视的目录中,将立即引发 OnCreated 事件,然后是一个或多个 OnChanged 事件。
Your FileSystemWatcher could be modified so that it doesn't do its read/rename during the "OnCreated" event, but rather:
您的 FileSystemWatcher 可以被修改,以便它不会在“OnCreated”事件期间进行读取/重命名,而是:
- Spanws a thread that polls the file status until it is not locked (using a FileInfo object)
- Calls back into the service to process the file as soon as it determines the file is no longer locked and is ready to go
- 跨越一个线程轮询文件状态直到它没有被锁定(使用 FileInfo 对象)
- 一旦确定文件不再锁定并准备好,就回调服务以处理文件
回答by aku
In most cases simple approach like @harpo suggested will work. You can develop more sophisticated code using this approach:
在大多数情况下,像@harpo 建议的简单方法会起作用。您可以使用这种方法开发更复杂的代码:
- Find all opened handles for selected file using SystemHandleInformation\SystemProcessInformation
- Subclass WaitHandle class to gain access to it's internal handle
- Pass found handles wrapped in subclassed WaitHandle to WaitHandle.WaitAny method
- 使用 SystemHandleInformation\SystemProcessInformation 查找所选文件的所有打开的句柄
- 子类 WaitHandle 类以访问其内部句柄
- 将包装在子类 WaitHandle 中的找到的句柄传递给 WaitHandle.WaitAny 方法
回答by Rudi
Ad to transfer process trigger file SameNameASTrasferedFile.trg that is created after file transmission is completed.
在文件传输完成后创建的传输过程触发文件 SameNameASTrasferedFile.trg 的广告。
Then setup FileSystemWatcher that will fire event only on *.trg file.
然后设置 FileSystemWatcher,它只会在 *.trg 文件上触发事件。
回答by harpo
I don't know what you're using to determine the file's lock status, but something like this should do it.
我不知道你用什么来确定文件的锁定状态,但应该这样做。
while (true)
{
try {
stream = File.Open( fileName, fileMode );
break;
}
catch( FileIOException ) {
// check whether it's a lock problem
Thread.Sleep( 100 );
}
}

