문제

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

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

이상적인 솔루션은 스레드가 포기하기 전에 영원히 멈추지 않도록 시간 초과 기간을 갖는 것입니다.

편집하다:아래 솔루션 중 일부를 시도한 후 결국 체계 모든 파일이 다음 위치에 기록되도록 Path.GetTempFileName(), 그런 다음 File.Move() 최종 장소로.하자마자 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:

파일이 생성되는 즉시 onecreated 이벤트가 제기됩니다.파일이 시청 된 디렉토리로 복사되거나 전송되는 경우, onecreated 이벤트가 즉시 제기되고 하나 이상의 변경 된 이벤트가 이어집니다.

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

  1. 파일이 잠기지 않을 때까지 파일 상태를 폴링하는 스레드를 Spanws(FileInfo 개체 사용)
  2. 파일이 더 이상 잠겨 있지 않고 이동할 준비가 되었다고 판단하는 즉시 파일을 처리하기 위해 서비스를 다시 호출합니다.

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

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

AD 전송 프로세스 트리거 파일 파일 전송이 완료된 후 생성 된 SAMENAMEASTRASFEREDFILE.TRG.

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

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

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

        // check whether it's a lock problem

        Thread.Sleep( 100 );
    }
}

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

파일의 모든 변경에 대해 알림을 받고 알림을받을 때 현재 허용 된 답변에 명시된대로 잠겨 있는지 확인하십시오. 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);
}

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

나는 Gulzar와 같은 방식으로 루프를 계속 시도합니다.

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

간단히 변경됨 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);
    }
}

물론 파일 크기가 생성 시 미리 할당되어 있으면 오탐지가 발생합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top