.NET에서 파일이 잠금 해제 될 때까지 기다립니다.
파일의 잠금이 해제되고 읽기 및 이름 바꾸기에 액세스 할 수있을 때까지 스레드를 차단하는 가장 간단한 방법은 무엇입니까? 예를 들어 .NET Framework 어딘가에 WaitOnFile ()이 있습니까?
FileSystemWatcher를 사용하여 FTP 사이트로 전송할 파일을 찾는 서비스가 있지만 다른 프로세스가 파일 쓰기를 완료하기 전에 파일 생성 이벤트가 발생합니다.
이상적인 솔루션은 시간 초과 기간이 있으므로 포기하기 전에 스레드가 영원히 중단되지 않습니다.
편집 : 아래 솔루션 중 일부를 시도한 후 모든 파일이에 기록되도록 시스템 을 변경 Path.GetTempFileName()
한 다음 File.Move()
최종 위치로 a 를 수행했습니다 . 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에서 노출되지 않으므로 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
}
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.
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.
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.
Good luck!
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.
From 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.
Your FileSystemWatcher could be modified so that it doesn't do its read/rename during the "OnCreated" event, but rather:
- 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
In most cases simple approach like @harpo suggested will work. You can develop more sophisticated code using this approach:
- 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
Ad to transfer process trigger file SameNameASTrasferedFile.trg that is created after file transmission is completed.
Then setup FileSystemWatcher that will fire event only on *.trg file.
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 ); } }
A possible solution would be, to combine a filesystemwatcher with some polling,
get Notified for every Change on a File, and when getting notified check if it is locked as stated in the currently accepted answer: https://stackoverflow.com/a/50800/6754146 The code for opening the filestream is copied from the answer and slightly modified:
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);
}
With this way you can Check for a file if its locked and get notified when its closed over the specified callback, this way you avoid the overly aggressive polling and only do the work when it may be actually be closed
I do it the same way as Gulzar, just keep trying with a loop.
In fact I don't even bother with the file system watcher. Polling a network drive for new files once a minute is cheap.
Simply use the Changed event with the NotifyFilter NotifyFilters.LastWrite:
var watcher = new FileSystemWatcher {
Path = @"c:\temp\test",
Filter = "*.xml",
NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += watcher_Changed;
watcher.EnableRaisingEvents = true;
I ran into a similar issue when adding an outlook attachment. "Using" saved the day.
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);
How about this as an option:
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);
}
}
Of course if the filesize is preallocated on the create you'd get a false positive.
참고URL : https://stackoverflow.com/questions/50744/wait-until-file-is-unlocked-in-net
'IT story' 카테고리의 다른 글
ng-model을 사용하여 날짜 형식을 지정하는 방법은 무엇입니까? (0) | 2020.09.05 |
---|---|
PowerShell에서 "%"(퍼센트)는 무엇을합니까? (0) | 2020.09.05 |
Android : Activity.runOnUiThread와 View.post의 차이점은 무엇입니까? (0) | 2020.09.05 |
Eclipse에서 pep8.py를 통합하는 방법은 무엇입니까? (0) | 2020.09.05 |
Google의 Imageless 버튼 (0) | 2020.09.05 |