Подождите, пока файл не будет разблокирован in.NET
каков самый простой способ блокировки потока до тех пор, пока файл не будет разблокирован и доступен для чтения и переименования? Например, есть ли WaitOnFile () где-то в .NET Framework?
у меня есть служба, которая использует FileSystemWatcher для поиска файлов, которые должны быть переданы на FTP-сайт, но создано событие срабатывает до того, как другой процесс закончит запись файла.
идеальное решение будет иметь период ожидания, так что нить не висит вечно, прежде чем сдаться.
Edit: после того, как я попробовал некоторые из решений ниже, я в конечном итоге изменил система чтобы все файлы писали в Path.GetTempFileName()
, то выполняется File.Move()
до конечного местоположения. Как только FileSystemWatcher
событие запущено, файл уже был завершен.
15 ответов:
Это был ответ, который я дал на вопрос:
/// <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; }
Запуск от ответа Эрика, я включил некоторые улучшения, чтобы сделать код гораздо более легкой и компактной. Надеюсь, что это полезно.
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, поэтому я добавил небольшую функцию взлома (
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; }
Я собрал вспомогательный класс для таких вещей. Это будет работать, если у вас есть контроль над всем, что бы открыть файл. Если вы ожидаете конкуренции от множества других вещей, то это довольно бесполезно.
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 }
он работает с использованием именованного мьютекса. Те, кто хочет получить доступ к файлу, пытаются получить контроль над именованным мьютексом, который разделяет имя файла (с '\'превратился в '/'s). Вы можете использовать Open(), который будет останавливаться до тех пор, пока мьютекс доступен или вы можете использовать TryOpen(TimeSpan), который пытается получить мьютекс для заданной продолжительности и возвращает false, если он не может получить в течение промежутка времени. Это, скорее всего, должно использоваться внутри блока using, чтобы гарантировать, что блокировки будут освобождены должным образом, и поток (если он открыт) будет правильно удален, когда этот объект будет удален.
Я сделал быстрый тест с ~20 вещей, чтобы сделать различных чтения/записи файла и не увидел тления. Очевидно, что это не очень расширенный, но он должен работать для большинства простых случаев.
для этого конкретного приложения непосредственное наблюдение за файлом неизбежно приведет к трудной для отслеживания ошибке, особенно когда размер файла увеличивается. Вот две различные стратегии, которые будут работать.
- Ftp два файла, но только смотреть один. Например, отправить файлы важно.TXT и важно.заканчивать. Только смотреть на финишный файл, но обрабатывать txt.
- FTP один файл, но переименовать его, когда сделано. Например отправить важно.подождите и переименуйте отправителя это важно.тхт, когда закончите.
удачи!
один из методов, который я использовал некоторое время назад, состоял в том, чтобы написать свою собственную функцию. В основном поймать исключение и повторить попытку с помощью таймера, который вы можете запустить в течение заданного времени. Если есть лучший способ, пожалуйста, поделитесь.
с MSDN:
OnCreated событие возникает, как только как создается файл. Если файл быть скопированы или перенесены в наблюдаемый каталог, событие OnCreated будет поднят немедленно, а затем одним или несколькими событиями OnChanged.
ваш FileSystemWatcher может быть изменен так, чтобы он не выполнял свое чтение/переименование во время события "OnCreated", а скорее:
- Spanws поток, который опрашивает состояние файла, пока он не заблокирован (с помощью объекта FileInfo)
- вызывает обратно в службу для обработки файла, как только он определяет, что файл больше не заблокирован и готов к работе
в большинстве случаев простой подход, как @harpo предложил будет работать. Вы можете разработать более сложный код, используя этот подход:
- найти все открытые дескрипторы для выбранного файла с помощью SystemHandleInformation\SystemProcessInformation
- подкласс WaitHandle класс, чтобы получить доступ к его внутреннему дескриптору
- передать найденные дескрипторы, завернутые в подкласс WaitHandle к WaitHandle.Метод WaitAny
Ad для передачи файла триггера процесса SameNameASTrasferedFile.trg это создается после завершения передачи файлов.
затем установите FileSystemWatcher, который будет запускать событие только на *.файл trg.
Я не знаю, что вы используете для определения состояния блокировки файла, но что-то вроде этого должно сделать это.
while (true) { try { stream = File.Open( fileName, fileMode ); break; } catch( FileIOException ) { // check whether it's a lock problem Thread.Sleep( 100 ); } }
возможным решением было бы объединить filesystemwatcher с некоторым опросом,
получать уведомления для каждого изменения в файле, и при получении уведомления проверить, если это так заблокировано, как указано в принятом в настоящее время ответе:https://stackoverflow.com/a/50800/6754146 Код для открытия filestream копируется из ответа и слегка модифицируется:
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); }
таким образом, вы можете проверить файл, если он заблокирован и получить уведомление, когда его закрыли по указанному обратному вызову таким образом вы избегаете чрезмерно агрессивного опроса и выполняете работу только тогда, когда он может быть фактически закрыт
Я делаю это так же, как Гульзар, просто продолжайте пытаться с петлей.
на самом деле я даже не беспокоюсь о наблюдателе файловой системы. Опрос сетевого диска для новых файлов один раз в минуту является дешевым.
просто использовать изменить событие с NotifyFilter NotifyFilters.LastWrite:
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); } }
конечно, если размер файла предварительно выделен на создание, вы получите ложное срабатывание.