- код имеет записывающее устройство в качестве экземпляра var, но использует статическую блокировку.Если у вас было несколько экземпляров, записывающих в разные файлы, нет никаких причин, по которым им нужно было бы использовать одну и ту же блокировку
- для связанной заметки, поскольку у вас уже есть средство записи (как частный экземпляр var), вы можете использовать этодля блокировки вместо использования отдельного объекта locker в этом случае - это делает вещи немного проще.
«Правильный ответ» действительно зависит от того, что вы ищете с точки зрения поведения блокировки / блокировки,Например, самой простой вещью было бы пропустить промежуточную структуру данных, просто имея метод WriteValues, чтобы каждый поток, «сообщающий» о своих результатах, продолжал и записывал их в файл.Что-то вроде:
StreamWriter writer = new StreamWriter("file");
public void WriteValues(IEnumerable<double> values)
{
lock (writer)
{
foreach (var d in values)
{
writer.WriteLine(d);
}
writer.Flush();
}
}
Конечно, это означает, что рабочие потоки сериализуются во время их фаз «отчета о результатах» - в зависимости от характеристик производительности, что может быть вполне приемлемым (5 минут для генерации, 500 мс для записи,например).
На другом конце спектра рабочие потоки будут записывать в структуру данных.Если вы работаете в .NET 4, я бы порекомендовал просто использовать ConcurrentQueue , а не делать это самостоятельно.
Кроме того, вы можете захотеть сделать файл ввода / вывода более крупнымпакетов, чем те, о которых сообщают рабочие потоки, так что вы можете просто делать запись в фоновом потоке с определенной частотой.Этот конец спектра выглядит примерно так (вы бы удалили вызовы Console.WriteLine в реальном коде, они просто есть, чтобы вы могли видеть, как он работает в действии)
public class ThreadSafeFileBuffer<T> : IDisposable
{
private readonly StreamWriter m_writer;
private readonly ConcurrentQueue<T> m_buffer = new ConcurrentQueue<T>();
private readonly Timer m_timer;
public ThreadSafeFileBuffer(string filePath, int flushPeriodInSeconds = 5)
{
m_writer = new StreamWriter(filePath);
var flushPeriod = TimeSpan.FromSeconds(flushPeriodInSeconds);
m_timer = new Timer(FlushBuffer, null, flushPeriod, flushPeriod);
}
public void AddResult(T result)
{
m_buffer.Enqueue(result);
Console.WriteLine("Buffer is up to {0} elements", m_buffer.Count);
}
public void Dispose()
{
Console.WriteLine("Turning off timer");
m_timer.Dispose();
Console.WriteLine("Flushing final buffer output");
FlushBuffer(); // flush anything left over in the buffer
Console.WriteLine("Closing file");
m_writer.Dispose();
}
/// <summary>
/// Since this is only done by one thread at a time (almost always the background flush thread, but one time via Dispose), no need to lock
/// </summary>
/// <param name="unused"></param>
private void FlushBuffer(object unused = null)
{
T current;
while (m_buffer.TryDequeue(out current))
{
Console.WriteLine("Buffer is down to {0} elements", m_buffer.Count);
m_writer.WriteLine(current);
}
m_writer.Flush();
}
}
class Program
{
static void Main(string[] args)
{
var tempFile = Path.GetTempFileName();
using (var resultsBuffer = new ThreadSafeFileBuffer<double>(tempFile))
{
Parallel.For(0, 100, i =>
{
// simulate some 'real work' by waiting for awhile
var sleepTime = new Random().Next(10000);
Console.WriteLine("Thread {0} doing work for {1} ms", Thread.CurrentThread.ManagedThreadId, sleepTime);
Thread.Sleep(sleepTime);
resultsBuffer.AddResult(Math.PI*i);
});
}
foreach (var resultLine in File.ReadAllLines(tempFile))
{
Console.WriteLine("Line from result: {0}", resultLine);
}
}
}