파일시스템 감시

파일시스템 감시

.NET에서는 파일 시스템을 감시하기 위한 기능으로 FileSystemWatcher 클래스를 제공합니다. 이 클래스는 특정 디렉터리에서 파일이나 폴더에 대한 변경 사항을 감시할 수 있으며, 파일의 생성, 삭제, 변경, 이름 변경 등의 이벤트를 처리할 수 있습니다.

주요 기능 소개

FileSystemWatcher는 다음과 같은 상황을 감지할 수 있습니다.

  • 파일/폴더 생성(Create): 새로운 파일이나 폴더가 생성될 때 발생합니다.
  • 파일/폴더 삭제(Delete): 파일이나 폴더가 삭제될 때 발생합니다.
  • 파일 변경(Change): 파일 내용이 변경되거나 속성이 변경될 때 발생합니다.
  • 파일/폴더 이름 변경(Rename): 파일이나 폴더의 이름이 변경될 때 발생합니다. 이 클래스를 사용하면 폴더나 파일을 실시간으로 감시하고, 각 이벤트가 발생할 때 특정 작업을 수행하도록 할 수 있습니다.

FileSystemWatcher 클래스 사용 예제

예제 1

다음은 폴더와 파일의 변경 사항을 감시하는 간단한 예제입니다.

using System;
using System.IO;
class Program
{
    static void Main()
    {
        // 감시할 경로 지정
        string pathToWatch = @"C:\TestFolder";
        // FileSystemWatcher 인스턴스 생성
        FileSystemWatcher watcher = new FileSystemWatcher();
        watcher.Path = pathToWatch;
        // 감시할 이벤트 설정
        watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite;
        // 파일 및 디렉터리에서 발생하는 변경 사항에 대해 이벤트 처리기 연결
        watcher.Created += OnChanged;
        watcher.Deleted += OnChanged;
        watcher.Renamed += OnRenamed;
        watcher.Changed += OnChanged;
        // 감시를 시작
        watcher.EnableRaisingEvents = true;
        Console.WriteLine($"Watching {pathToWatch}... Press 'q' to quit.");
        while (Console.Read() != 'q');
    }
    // 파일이 생성, 삭제, 변경되었을 때 처리하는 메서드
    private static void OnChanged(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine($"File: {e.FullPath} {e.ChangeType}");
    }
    // 파일이 이름이 변경되었을 때 처리하는 메서드
    private static void OnRenamed(object sender, RenamedEventArgs e)
    {
        Console.WriteLine($"File: {e.OldFullPath} renamed to {e.FullPath}");
    }
}
  • FileSystemWatcher 인스턴스 생성: 감시할 폴더를 설정하고, 해당 폴더의 파일이나 서브 폴더에 발생하는 이벤트를 감시하도록 설정합니다.

  • NotifyFilter 설정: 변경을 감시할 파일 속성이나 동작을 설정합니다. 이 예제에서는 파일 이름, 디렉터리 이름, 마지막 쓰기 시간에 대한 변경을 감시합니다.

  • 이벤트 처리기 연결: Created, Deleted, Changed, Renamed 이벤트에 대한 처리기를 연결하여 각각의 상황에 맞는 로그를 출력합니다.

  • EnableRaisingEvents: 감시를 시작하는 속성으로, 이 값을 true로 설정해야 이벤트가 발생합니다.

  • 이벤트 처리: 각 이벤트가 발생할 때마다 OnChanged 또는 OnRenamed 메서드가 호출되어 해당 파일 또는 폴더의 변경 사항을 출력합니다.

예제 2

다음은 파일이 생성될 때, 해당 파일의 내용을 읽어서 콘솔에 출력하는 예제입니다.

using System;
using System.IO;
class Program
{
    static void Main()
    {
        // 감시할 경로 지정
        string pathToWatch = @"C:\TestFolder";
        // FileSystemWatcher 인스턴스 생성
        FileSystemWatcher watcher = new FileSystemWatcher();
        watcher.Path = pathToWatch;
        // 감시할 이벤트 설정
        watcher.NotifyFilter = NotifyFilters.FileName;
        // 파일 생성 이벤트에 대해 이벤트 처리기 연결
        watcher.Created += OnCreated;
        // 감시를 시작
        watcher.EnableRaisingEvents = true;
        Console.WriteLine($"Watching {pathToWatch}... Press 'q' to quit.");
        while (Console.Read() != 'q');
    }
    // 파일이 생성되었을 때 처리하는 메서드
    private static void OnCreated(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine($"File created: {e.FullPath}");
        
        // 파일이 생성된 후 읽기
        try
        {
            // 텍스트 파일인지 확인
            if (Path.GetExtension(e.FullPath) == ".txt")
            {
                // 파일이 생성된 후 약간의 지연 시간 필요 (파일이 아직 열려 있을 수 있음)
                System.Threading.Thread.Sleep(100); 
                // 파일 내용 읽기
                string fileContent = File.ReadAllText(e.FullPath);
                Console.WriteLine("File content:");
                Console.WriteLine(fileContent);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error reading file: {ex.Message}");
        }
    }
}
  • 파일 생성 감시: watcher.Created 이벤트에 대한 처리기인 OnCreated 메서드를 설정합니다.

  • 파일 생성 시 처리: OnCreated 메서드는 파일이 생성되면 해당 파일의 경로를 출력하고, 그 파일이 .txt 파일인 경우에만 파일의 내용을 읽습니다.

  • 파일 읽기: File.ReadAllText를 사용하여 생성된 파일의 내용을 읽고 콘솔에 출력합니다.

  • 지연 처리: 파일이 생성된 직후에 파일이 아직 다른 프로세스에 의해 열려 있을 수 있기 때문에 Thread.Sleep(100)을 통해 약간의 지연을 추가하여 파일을 읽는 과정에서 발생할 수 있는 오류를 방지합니다.

주의 사항

  • 파일이 생성되면서 바로 사용 중인 경우: 파일이 여전히 작성 중일 경우, 읽기 도중 예외가 발생할 수 있습니다. 이를 방지하려면 파일이 사용 가능한 상태인지 확인하거나 추가적인 오류 처리를 할 수 있습니다.
  • 파일 형식 검사: 텍스트 파일인지 확인하기 위해 .txt 확장자를 검사하고 있습니다. 필요한 경우 다른 파일 형식도 처리하도록 수정할 수 있습니다.

실무 활용

FileSystemWatcher를 사용할 때는 여러 가지 주의 사항이 있으며, 특히 성능에 영향을 줄 수 있는 요소들을 고려해야 합니다.

감시할 이벤트 선택

NotifyFilter는 감시할 이벤트의 종류를 지정하는 속성입니다. 감시 항목이 많을수록 시스템 리소스를 더 많이 사용할 수 있습니다. 예를 들어, 파일 생성만 감시하려면 NotifyFilters.FileName만 설정하는 것이 효율적입니다.

// 파일 생성만 감시
watcher.NotifyFilter = NotifyFilters.FileName;

기본적으로 여러 항목을 감시하면 이벤트가 많이 발생하고, 이벤트 핸들러가 자주 호출되기 때문에 성능에 부정적인 영향을 미칠 수 있습니다. 따라서 필요한 이벤트만 감시하는 것이 좋습니다.

내부 버퍼 크기 조정

FileSystemWatcher는 내부적으로 버퍼를 사용해 파일 시스템 이벤트를 처리합니다. 파일 변경 이벤트가 매우 자주 발생하는 경우, 내부 버퍼가 가득 차면 버퍼 오버플로우가 발생할 수 있습니다. 버퍼가 가득 차면 FileSystemWatcher는 더 이상 이벤트를 처리하지 못하고 Error 이벤트가 발생합니다. 따라서 감시하는 파일 시스템의 활동량이 많을 경우, 버퍼 크기를 적절히 조정하는 것이 필요합니다.

// 버퍼 크기 조정 (기본값은 8192 바이트)
watcher.InternalBufferSize = 32768;  // 32KB로 설정

하지만 너무 큰 버퍼 크기를 설정하면 메모리 사용량이 늘어날 수 있으므로, 시스템 성능에 따라 적절한 값을 설정하는 것이 중요합니다.

하위 디렉터리 감시 여부 설정

IncludeSubdirectories 속성을 설정하여 하위 디렉터리까지 감시할지 결정할 수 있습니다. 기본값은 false이며, 하위 디렉터리까지 감시할 경우 이벤트 발생이 증가할 수 있습니다. 많은 하위 폴더를 포함하는 대규모 디렉터리 구조를 감시할 때는 성능 문제가 발생할 수 있으므로 주의해야 합니다.

// 하위 디렉터리 포함 설정
watcher.IncludeSubdirectories = true;

처리할 이벤트 수 제한

이벤트 핸들러에서 처리할 수 있는 이벤트의 수가 너무 많아지면 응답 속도가 느려질 수 있습니다. 감시할 이벤트의 수를 최소화하거나, 특정 조건에 따라 이벤트를 무시하는 논리를 추가하여 이벤트 처리 부담을 줄일 수 있습니다.

 // 조건에 따른 이벤트 무시
private static void OnChanged(object sender, FileSystemEventArgs e)
{
    if (!e.FullPath.EndsWith(".txt"))
    {
        return;  // .txt 파일이 아니면 무시
    }
    Console.WriteLine($"File changed: {e.FullPath}");
}

파일 잠금 문제

파일이 생성되거나 수정된 직후에 다른 프로세스가 해당 파일을 아직 사용하고 있을 수 있습니다. 이를 방지하기 위해 이벤트 발생 직후 바로 파일에 접근하지 않고, 약간의 지연 시간을 주거나 파일의 상태를 확인 후 처리하는 로직이 필요할 수 있습니다.

비동기 처리 고려

파일 시스템에서 매우 많은 이벤트가 발생하는 경우, 이벤트 핸들러에서 비동기 처리를 사용하여 메인 스레드의 부하를 줄이는 것이 좋습니다. 이는 UI 응답성이나 전체적인 시스템 성능을 향상시키는 데 도움이 됩니다.

private static async void OnChangedAsync(object sender, FileSystemEventArgs e)
{
    await Task.Run(() =>
    {
        // 파일 내용 읽기
        if (File.Exists(e.FullPath))
        {
            string content = File.ReadAllText(e.FullPath);
            Console.WriteLine($"File content: {content}");
        }
    });
}

중복 이벤트 처리 방지

파일이 한 번 변경되었을 때 여러 번의 이벤트가 발생할 수 있습니다. 특히, LastWriteFileName 같은 필터가 동시에 설정된 경우 파일 생성 후 곧바로 내용이 수정되면 여러 이벤트가 발생할 수 있습니다. 이를 방지하기 위해, 특정 시간 내에 발생한 중복 이벤트를 무시하는 로직을 추가할 수 있습니다.

private static DateTime lastReadTime;
private static void OnChanged(object sender, FileSystemEventArgs e)
{
    if ((DateTime.Now - lastReadTime).TotalMilliseconds < 500)
    {
        return;  // 500ms 내에 발생한 중복 이벤트 무시
    }
    lastReadTime = DateTime.Now;
    Console.WriteLine($"File changed: {e.FullPath}");
}