[.net] .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
}

명명 된 뮤텍스를 사용하여 작동합니다. 파일에 액세스하려는 사람들은 파일 이름을 공유하는 명명 된 뮤텍스의 제어권을 얻으려고 시도합니다 ( ‘\’는 ‘/’로 바뀜). 뮤텍스에 액세스 할 수있을 때까지 중단되는 Open ()을 사용하거나 주어진 기간 동안 뮤텍스를 획득하려고 시도하고 시간 범위 내에 획득 할 수 없으면 false를 반환하는 TryOpen (TimeSpan)을 사용할 수 있습니다. 잠금이 적절하게 해제되고이 개체가 삭제 될 때 스트림 (열린 경우)이 적절하게 삭제되도록하기 위해 using 블록 내에서 사용할 가능성이 가장 높습니다.

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


답변

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

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

행운을 빕니다!


답변

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


답변

에서 MSDN :

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

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

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