线程安全的StreamWriter C#怎么做? 2

所以这是我上一个问题的延续 – 所以问题是“构建一个线程安全的程序的最佳方法是什么,它需要将双值写入文件。如果通过streamwriter保存值的函数是multithreading调用的?最好的方法是什么?“

我修改了在MSDN上找到的一些代码,以下怎么样? 这个正确地将所有内容写入文件。

namespace SafeThread { class Program { static void Main() { Threading threader = new Threading(); AutoResetEvent autoEvent = new AutoResetEvent(false); Thread regularThread = new Thread(new ThreadStart(threader.ThreadMethod)); regularThread.Start(); ThreadPool.QueueUserWorkItem(new WaitCallback(threader.WorkMethod), autoEvent); // Wait for foreground thread to end. regularThread.Join(); // Wait for background thread to end. autoEvent.WaitOne(); } } class Threading { List Values = new List(); static readonly Object locker = new Object(); StreamWriter writer = new StreamWriter("file"); static int bulkCount = 0; static int bulkSize = 100000; public void ThreadMethod() { lock (locker) { while (bulkCount < bulkSize) Values.Add(bulkCount++); } bulkCount = 0; } public void WorkMethod(object stateInfo) { lock (locker) { foreach (double V in Values) { writer.WriteLine(V); writer.Flush(); } } // Signal that this thread is finished. ((AutoResetEvent)stateInfo).Set(); } } } 

ThreadQueueUserWorkItem用于线程最低可用API 。 除非我绝对没有其他选择,否则我不会使用它们。 尝试使用Task类进行更高级别的抽象。 有关详细信息, 请参阅我最近关于此主题的博客文章 。

您还可以使用BlockingCollection作为正确的生产者/消费者队列,而不是尝试使用最低的可用API进行同步构建。

正确地重新制造这些车轮是非常困难的。 我强烈建议使用为此类需求而设计的类( TaskBlockingCollection ,具体而言)。 它们内置于.NET 4.0框架中, 可作为.NET 3.5的附加组件使用 。

  • 代码将writer作为实例var但使用静态锁定器。 如果你有多个实例写入不同的文件,他们没有理由需要共享同一个锁
  • 在相关的说明中,由于您已经拥有了编写器(作为私有实例var),您可以使用它来锁定而不是在这种情况下使用单独的锁定器对象 – 这使事情变得更简单。

“正确答案”实际上取决于您在锁定/阻止行为方面所寻求的内容。 例如,最简单的方法是跳过中间数据结构,只需要一个WriteValues方法,这样每个线程“报告”其结果,然后将它们写入文件。 就像是:

 StreamWriter writer = new StreamWriter("file"); public void WriteValues(IEnumerable values) { lock (writer) { foreach (var d in values) { writer.WriteLine(d); } writer.Flush(); } } 

当然,这意味着工作线程在其“报告结果”阶段进行序列化 – 取决于性能特征,虽然可能很好(例如,生成5分钟,写入500毫秒)。

另一方面,您将工作线程写入数据结构。 如果您使用的是.NET 4,我建议您使用ConcurrentQueue而不是自己锁定。

此外,您可能希望以比工作线程报告的更大批量执行文件i / o,因此您可能选择仅在某个频率上编写后台线程。 频谱的那一端看起来像下面的那样(你将删除实际代码中的Console.WriteLine调用,那些就是那里你可以看到它在运行中)

 public class ThreadSafeFileBuffer : IDisposable { private readonly StreamWriter m_writer; private readonly ConcurrentQueue m_buffer = new ConcurrentQueue(); 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(); } ///  /// 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 ///  ///  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(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); } } } 

所以你说你想要一堆线程使用StreamWriter将数据写入单个文件? 简单。 只需锁定StreamWriter对象即可。

这里的代码将创建5个线程。 每个线程将执行5个“动作”,并且在每个动作结束时,它将向名为“file”的文件写入5行。

 using System; using System.Collections.Generic; using System.IO; using System.Threading; namespace ConsoleApplication1 { class Program { static void Main() { StreamWriter Writer = new StreamWriter("file"); Action ThreadProcedure = (i) => { // A thread may perform many actions and write out the result after each action // The outer loop here represents the multiple actions this thread will take for (int x = 0; x < 5; x++) { // Here is where the thread would generate the data for this action // Well simulate work time using a call to Sleep Thread.Sleep(1000); // After generating the data the thread needs to lock the Writer before using it. lock (Writer) { // Here we'll write a few lines to the Writer for (int y = 0; y < 5; y++) { Writer.WriteLine("Thread id = {0}; Action id = {1}; Line id = {2}", i, x, y); } } } }; //Now that we have a delegate for the thread code lets make a few instances List AsyncResultList = new List(); for (int w = 0; w < 5; w++) { AsyncResultList.Add(ThreadProcedure.BeginInvoke(w, null, null)); } // Wait for all threads to complete foreach (IAsyncResult r in AsyncResultList) { r.AsyncWaitHandle.WaitOne(); } // Flush/Close the writer so all data goes to disk Writer.Flush(); Writer.Close(); } } } 

结果应该是一个文件“文件”,其中包含125行,同时执行所有“操作”,并且每个操作的结果同步写入文件。

你在那里的代码被巧妙地打破 – 特别是,如果排队的工作项首先运行,那么它将立即刷新(空)值列表,然后终止,之后你的工人去填充列表(这将最终被忽略了)。 自动重置事件也没有任何作用,因为没有任何查询或等待其状态。

此外,由于每个线程使用不同的锁,锁没有意义! 每次访问该编写器时,您都需要确保持有一个共享锁。 您不需要在刷新代码和生成代码之间进行锁定; 你需要确保在代完成后刷新运行。

尽管如此,你可能在正确的轨道上 – 尽管我使用固定大小的数组而不是列表,并在数组满时刷新数组中的所有条目。 如果线程长寿,这可以避免内存不足的可能性。