.NET에서 파일이 잠금 해제 될 때까지 기다립니다.


103

파일의 잠금이 해제되고 읽기 및 이름 바꾸기에 액세스 할 수있을 때까지 스레드를 차단하는 가장 간단한 방법은 무엇입니까? 예를 들어 .NET Framework 어딘가에 WaitOnFile ()이 있습니까?

FileSystemWatcher를 사용하여 FTP 사이트로 전송할 파일을 찾는 서비스가 있지만 다른 프로세스가 파일 쓰기를 완료하기 전에 파일 생성 이벤트가 발생합니다.

이상적인 솔루션은 시간 초과 기간이 있으므로 포기하기 전에 스레드가 영원히 중단되지 않습니다.

편집 : 아래 솔루션 중 일부를 시도한 후 모든 파일이에 기록되도록 시스템 을 변경 Path.GetTempFileName()한 다음 File.Move()최종 위치로 a 를 수행했습니다 . FileSystemWatcher이벤트가 발생 하자마자 파일이 이미 완료되었습니다.


4
.NET 4.0 출시 이후이 문제를 해결하는 더 좋은 방법이 있습니까?
jason

답변:


40

이것은 내가 관련된 질문 에 대한 대답이었습니다 .

    /// <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;
    }

8
나는 이것이 추악하지만 가능한 유일한 해결책을
찾는다

6
이것이 일반적인 경우에 실제로 작동할까요? using () 절에서 파일을 열면 using 범위가 종료 될 때 파일이 닫히고 잠금 해제됩니다. 이와 동일한 전략을 사용하는 두 번째 프로세스 (반복 재시도)가있는 경우 WaitForFile ()이 종료 된 후 파일을 열 수 있는지 여부에 대한 경쟁 조건이 있습니다. 아니?
Cheeso 2009-06-13

75
나쁜 생각! 개념이 옳지 만 더 나은 해결책은 bool 대신 FileStream을 반환하는 것입니다. 사용자가 파일에 자신의 잠금을 얻을 수있는 기회를 얻었하기 전에 파일을 다시 잠겨있는 경우 - 그는이 기능이 "false"를 반환하는 경우에도 예외를 얻을 것이다
Nissim

2
Fero의 방법은 어디에 있습니까?
Vbp

1
Nissim의 의견은 내가 생각했던 것과 정확히 일치하지만 해당 검색을 사용하려는 경우 바이트를 읽은 후 0으로 재설정하는 것을 잊지 마십시오. fs.Seek (0, SeekOrigin.Begin);
WHol 2015-08-27

73

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;
}

16
나는이 코드가 여전히 매력처럼 작동한다고 말하기 위해 미래에서 왔습니다. 감사.
OnoSendai 2013

6
@PabloCosta 맞아요! 그것은 할 수없는 목적을 물리 치고, 그것을 그랬다면 때문에, 닫습니다 다른 스레드 힘 경주 엽니 다. 이 구현은 열려 있기 때문에 정확합니다! 호출자가 그것에 대해 걱정하게하십시오 using. null에 대해 안전 하고 using블록 내부에서 null을 확인하십시오 .
doug65536

2
"FileStream fs = null;" try 외부에서 선언해야하지만 for 내부에서 선언해야합니다. 그런 다음 try 내에서 fs를 할당하고 사용하십시오. catch 블록은 "if (fs! = null) fs.Dispose ();"를 수행해야합니다. (또는 C # 6의 fs? .Dispose ()) 반환되지 않는 FileStream이 제대로 정리되었는지 확인합니다.
Bill Menees 2010

1
바이트를 읽는 것이 정말로 필요합니까? 내 경험상 읽기 액세스를 위해 파일을 열었다면 파일이 있고 테스트 할 필요가 없습니다. 여기서 디자인을 사용하면 독점 액세스를 강요하지 않으므로 첫 번째 바이트는 읽을 수 있지만 다른 바이트는 읽을 수 없습니다 (바이트 수준 잠금). 원래 질문에서 읽기 전용 공유 수준으로 열 수 있으므로 다른 프로세스가 파일을 잠 그거나 수정할 수 없습니다. 어쨌든 fs.ReadByte ()는 사용량에 따라 완전히 낭비되거나 충분하지 않다고 생각합니다.
eselk

8
블록 fs에서 어떤 상황이 null 이 될 수 catch없습니까? 경우 FileStream생성자가 발생, 변수는 값이 할당되지 않고, 내부 아무 것도 없다 try을 던질 수 있습니다 IOException. 나에게는 그냥하는 것이 괜찮을 것 같다 return new FileStream(...).
Matti Virkkunen

18

다음은 파일 작업 자체와는 별개로이를 수행하는 일반 코드입니다. 다음은 사용 방법에 대한 예입니다.

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;
    }

5
그들은 정말로 SharingViolationException. 실제로 .NET Framework에서 내려 오는 한 이전 버전과의 호환성을 유지할 수 있습니다 IOException. 그리고 그들은 정말로, 정말로 그래야합니다.
Roman Starkov


9
.NET Framework 4.5, .NET Standard 및 .NET Core에서 HResult는 Exception 클래스의 공용 속성입니다. 이를 위해 더 이상 반사가 필요하지 않습니다. MSDN에서 :Starting with the .NET Framework 4.5, the HResult property's setter is protected, whereas its getter is public. In previous versions of the .NET Framework, both getter and setter are protected.
NightOwl888

13

나는 이런 종류의 일을 위해 도우미 클래스를 모았습니다. 파일에 액세스하는 모든 것을 제어 할 수 있으면 작동합니다. 다른 많은 것들로부터 경쟁을 기대하고 있다면 이것은 꽤 쓸모가 없습니다.

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 ()을 사용하거나 주어진 기간 동안 뮤텍스를 획득하려고 시도하고 시간 범위 내에 획득 할 수 없으면 false를 반환하는 TryOpen (TimeSpan)을 사용할 수 있습니다. 잠금이 적절하게 해제되고이 개체가 삭제 될 때 스트림 (열린 경우)이 적절하게 삭제되도록하기 위해 using 블록 내에서 사용할 가능성이 가장 높습니다.

파일의 다양한 읽기 / 쓰기를 수행하기 위해 ~ 20 가지의 빠른 테스트를 수행했으며 손상이없는 것을 확인했습니다. 분명히 고급은 아니지만 대부분의 간단한 경우에서 작동합니다.


5

이 특정 응용 프로그램의 경우 파일을 직접 관찰하면 특히 파일 크기가 증가 할 때 필연적으로 버그를 추적하기가 어렵습니다. 작동 할 두 가지 전략이 있습니다.

  • Ftp 두 파일이지만 하나만 시청하십시오. 예를 들어, important.txt 및 important.finish 파일을 보냅니다. 종료 파일 만 확인하고 txt를 처리합니다.
  • FTP 하나의 파일이지만 완료되면 이름을 바꿉니다. 예를 들어 important.wait를 보내고 보낸 사람이 완료되면 이름을 important.txt로 변경하도록합니다.

행운을 빕니다!


그것은 자동의 반대입니다. 이는 더 많은 단계를 통해 파일을 수동으로 가져 오는 것과 같습니다.
HackSlash

4

내가 예전에 사용한 기술 중 하나는 내 함수를 작성하는 것이 었습니다. 기본적으로 예외를 포착하고 지정된 기간 동안 실행할 수있는 타이머를 사용하여 재 시도합니다. 더 좋은 방법이 있다면 공유 해주세요.


3

에서 MSDN :

OnCreated 이벤트는 파일이 생성되는 즉시 발생합니다. 파일이 복사되거나 감시 된 디렉터리로 전송되는 경우 OnCreated 이벤트가 즉시 발생하고 하나 이상의 OnChanged 이벤트가 발생합니다.

FileSystemWatcher는 "OnCreated"이벤트 중에 읽기 / 이름 바꾸기를 수행하지 않고 다음과 같이 수정할 수 있습니다.

  1. 잠기지 않을 때까지 파일 상태를 폴링하는 스레드를 스팬합니다 (FileInfo 객체 사용).
  2. 파일이 더 이상 잠기지 않고 이동할 준비가되었음을 확인하는 즉시 서비스를 호출하여 파일을 처리합니다.

1
파일 시스템 감시자의 스레드를 생성하면 기본 버퍼가 오버플로되어 많은 변경된 파일이 누락 될 수 있습니다. 더 나은 접근 방식은 소비자 / 생산자 대기열을 만드는 것입니다.
Nissim

2

대부분의 경우 @harpo 제안과 같은 간단한 접근 방식이 작동합니다. 다음 접근 방식을 사용하여보다 정교한 코드를 개발할 수 있습니다.

  • SystemHandleInformation \ SystemProcessInformation을 사용하여 선택한 파일에 대해 열려있는 모든 핸들 찾기
  • 내부 핸들에 대한 액세스 권한을 얻기 위해 WaitHandle 클래스 하위 클래스
  • 서브 클래 싱 된 WaitHandle에 래핑 된 발견 된 핸들을 WaitHandle.WaitAny 메서드에 전달합니다.

2

파일 전송 완료 후 생성되는 전송 프로세스 트리거 파일 SameNameASTrasferedFile.trg에 대한 광고.

그런 다음 * .trg 파일에서만 이벤트를 발생시키는 FileSystemWatcher를 설정하십시오.


1

파일의 잠금 상태를 확인하기 위해 무엇을 사용하고 있는지 모르겠지만 이와 같은 작업을 수행해야합니다.

while (true)
{
    {
        stream = File.Open (fileName, fileMode);
        단절;
    }
    catch (FileIOException) {

        // 잠금 문제인지 확인

        Thread.Sleep (100);
    }
}

1
조금 늦었지만 파일이 잠기면 루프를 종료하지 않습니다. 카운터를 추가해야합니다 (첫 번째 답변 참조).
Peter

0

가능한 해결책은 파일 시스템 감시자와 폴링을 결합하는 것입니다.

파일의 모든 변경 사항에 대해 알림을 받고 알림을받을 때 현재 승인 된 답변에 명시된대로 잠겨 있는지 확인합니다. https://stackoverflow.com/a/50800/6754146 파일 스트림을 여는 코드는 답변에서 복사됩니다. 약간 수정되었습니다.

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);
}

이런 식으로 파일이 잠겨 있는지 확인하고 지정된 콜백을 통해 닫힐 때 알림을받을 수 있습니다. 이렇게하면 지나치게 공격적인 폴링을 피하고 실제로 닫힐 수있을 때만 작업을 수행 할 수 있습니다.


-1

나는 Gulzar와 같은 방식으로 수행합니다. 루프로 계속 시도하십시오.

사실 저는 파일 시스템 감시자도 신경 쓰지 않습니다. 1 분에 한 번씩 네트워크 드라이브에서 새 파일을 폴링하는 것은 저렴합니다.


2
저렴할 수 있지만 1 분에 한 번은 많은 응용 프로그램에 너무 깁니다. 때때로 실시간 모니터링이 필수적입니다. C # (이러한 것들에 가장 편리한 언어가 아님)으로 파일 시스템 메시지를 수신 할 무언가를 구현하는 대신 FSW를 사용합니다.
ThunderGr 2013

-1

NotifyFilter NotifyFilters.LastWrite 와 함께 Changed 이벤트를 사용하기 만하면됩니다 .

var watcher = new FileSystemWatcher {
      Path = @"c:\temp\test",
      Filter = "*.xml",
      NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += watcher_Changed; 
watcher.EnableRaisingEvents = true;

1
FileSystemWatcher는 파일 쓰기가 완료 될 때만 알리지 않습니다. "단일"논리적 쓰기에 대해 여러 번 알리는 경우가 많으며 첫 번째 알림을받은 후 파일을 열려고하면 예외가 발생합니다.
Ross

-1

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);

-3

옵션으로 어떻습니까?

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);
    }
}

물론 파일 크기가 생성시 사전 할당되면 오 탐지가 발생합니다.


1
파일에 쓰는 프로세스가 1 초 이상 일시 중지되거나 메모리에 1 초 이상 버퍼링되면 또 다른 오 탐지가 발생합니다. 나는 이것이 어떤 상황에서도 좋은 해결책이라고 생각하지 않습니다.
Chris Wenham
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.