파일에 여러 쓰레드에서 텍스트를 추가하기

파일에 여러 쓰레드에서 텍스트를 추가하기

이철우

어떤 파일에 텍스트를 추가하는 간단한 방법은

System.IO.File.AppendAllText(filePath, appendedText);

를 활용하는 것이다. 그런데 여러 쓰레드에서 한 파일에 이 함수를 호출하면 System.IO.IOException 이 발생할 수 있다. 이것을 피하는 클래스를 작성하였다.
ConcurrentQueue, CancellationTokenSource, Task 가 주요 역할을 한다.

    public class AppendTextQueue
    {
        private CancellationTokenSource? _cts = null;
        private readonly ConcurrentQueue<(string filePath, string appendedText)> _queue;

        public AppendTextQueue()
        {
            _queue = new ConcurrentQueue<(string filePath, string appendedText)>();
        }

        public async Task Enqueue(string filePath, string appendedText)
        {
            await Task.Run(() =>
            {
                _queue.Enqueue((filePath, appendedText));

                try
                {
                    if (_cts is not null)
                    {
                        _cts!.Cancel();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }).ConfigureAwait(false);
        }

        public async Task Dequeue(CancellationToken token)
        {
            await Task.Run(async () =>
            {
                while (!token.IsCancellationRequested)
                {
                    if (_queue.TryDequeue(out var item))
                    {
                        System.IO.File.AppendAllText(item.filePath, item.appendedText);
                    }
                    else
                    {
                        //_cts = new CancellationTokenSource();
                        _cts = CancellationTokenSource.CreateLinkedTokenSource(token);
                        try
                        {
                            await Task.Delay(Timeout.InfiniteTimeSpan, _cts.Token).ConfigureAwait(false);
                        }
                        catch (TaskCanceledException)
                        {
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.ToString());
                        }
                        finally
                        {
                            _cts = null;
                        }
                    }
                }
            }, token).ConfigureAwait(false);
        }
    }

이 클래스를 테스트 하는 코드는 아래와 같다.

Console.WriteLine("Hello, World!");

var filePaths = new List<string>
{
    @"D:\Temp\A.txt",
    @"D:\Temp\B.txt",
    @"D:\Temp\C.txt",
    @"D:\Temp\D.txt",
    @"D:\Temp\E.txt",
};
var texts = new List<string>
{
    "우리나라",
    "대한민국",
    "금수강산",
    "수신제가",
    "치국평천",
};

var queue = new Omnibus.Utils.File.AppendTextQueue();
using var cts = new CancellationTokenSource();
_ = queue.Dequeue(cts.Token);

var taskCount = 10;
var repeatCount = 100;
var startIndex = 0;
var itemCount = 5;

var random = new Random();

var tasks = new List<Task>();
for (int i = 0; i < taskCount; i++)
{
    var t = new Task(async () =>
    {
        for (int i = 0; i < repeatCount; i++)
        {
            await Task.Delay(random.Next(10, 50)).ConfigureAwait(false);
            var fileIndex = random.Next(startIndex, itemCount);
            var textIndex = random.Next(startIndex, itemCount);
            await queue.Enqueue(filePaths[fileIndex], texts[textIndex]).ConfigureAwait(false);
        }
    });
    tasks.Add(t);
    t.Start();
}
await Task.WhenAll([.. tasks]);
await Task.Delay(10000).ConfigureAwait(false);
cts.Cancel();

Console.WriteLine("Bye!");

퀴즈 1) 클래스 AppendTextQueue 의 메서드 Dequeue 는 중복해서 실행하면, System.IO.IOException 이 발생할 수 있다. 이를 방지하기 위한 코드를 작성하여라.

퀴즈 2) 클래스 AppendTextQueue 의 메서드 Dequeue 를 메서드 Run, Stop 형태로 바꾸어 보아라.

3개의 좋아요