파일에 여러 쓰레드에서 텍스트를 추가하기
이철우
어떤 파일에 텍스트를 추가하는 간단한 방법은
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 형태로 바꾸어 보아라.