C# 线程安全的StreamWriter 怎么做呢? 2
所以这是我上一个问题的延续 - 所以问题是 “构建线程安全的程序的最佳方法是什么,因为它需要将双精度值写入文件。如果通过流写入器保存值的函数被多个线程调用?最好的方法是什么? ?”
我修改了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<double> Values = new List<double>();
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();
}
}
}
So this is a continuation from my last question - So the question was
"What is the best way to build a program that is thread safe in terms that it needs to write double values to a file. If the function that saves the values via streamwriter is being called by multiple threads? Whats the best way of doing it?"
And I modified some code found at MSDN, how about the following? This one correctly writes everything to the file.
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<double> Values = new List<double>();
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();
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
Thread
和QueueUserWorkItem
是线程可用的最低 API。除非我最终别无选择,否则我不会使用它们。尝试使用Task
类以获得更高级别的抽象。有关详细信息,请参阅我最近关于该主题的博客文章。您还可以使用
BlockingCollection
作为适当的生产者/消费者队列,而不是尝试使用最低可用的同步 API 手动构建一个队列 >。正确地重新发明这些轮子是非常困难的。我强烈建议使用专为此类需求设计的类(具体来说,
Task
和BlockingCollection
)。它们内置于 .NET 4.0 框架和 作为 .NET 3.5 的附加组件提供。Thread
andQueueUserWorkItem
are the lowest available APIs for threading. I wouldn't use them unless I absolutely, finally, had no other choice. Try theTask
class for a much higher-level abstraction. For details, see my recent blog post on the subject.You can also use
BlockingCollection<double>
as a proper producer/consumer queue instead of trying to build one by hand with the lowest available APIs for synchronization.Reinventing these wheels correctly is surprisingly difficult. I highly recommend using the classes designed for this type of need (
Task
andBlockingCollection
, to be specific). They are built-in to the .NET 4.0 framework and are available as an add-on for .NET 3.5.“正确答案”实际上取决于您在锁定/阻止行为方面寻找的内容。例如,最简单的事情是跳过中间数据结构,只使用 WriteValues 方法,以便每个线程“报告”其结果并将其写入文件。类似于:
当然,这意味着工作线程在“报告结果”阶段进行序列化 - 根据性能特征,这可能就很好(例如,生成 5 分钟,写入 500 毫秒)。
另一方面,您可以让工作线程写入数据结构。如果您使用 .NET 4,我建议仅使用 ConcurrentQueue< /a> 而不是这样做锁定自己。
此外,您可能希望以比工作线程报告的批量更大的批量执行文件 I/O,因此您可能选择仅以某种频率在后台线程中进行写入。频谱的这一端看起来像下面这样(您可以在实际代码中删除 Console.WriteLine 调用,这些调用就在那里,这样您就可以看到它正在运行)
The 'right answer' really depends on what you're looking for in terms of locking/blocking behavior. For instance, the simplest thing would be to skip the intermediate data structure just have a WriteValues method such that each thread 'reporting' its results goes ahead and writes them to the file. Something like:
Of course, this means worker threads serialize during their 'report results' phases - depending on the performance characteristics, that may be just fine though (5 minutes to generate, 500ms to write, for example).
On the other end of the spectrum, you'd have the worker threads write to a data structure. If you're in .NET 4, I'd recommend just using a ConcurrentQueue rather than doing that locking yourself.
Also, you may want to do the file i/o in bigger batches than those being reported by the worker threads, so you might choose to just do writing in a background thread on some frequency. That end of the spectrum looks something like the below (you'd remove the Console.WriteLine calls in real code, those are just there so you can see it working in action)
所以你是说你想要一堆线程使用 StreamWriter 将数据写入单个文件?简单的。只需锁定 StreamWriter 对象即可。
这里的代码将创建 5 个线程。每个线程将执行 5 个“操作”,并且在每个操作结束时,它将向名为“file”的文件写入 5 行。
结果应该是一个包含 125 行的文件“文件”,其中所有“操作”同时执行,并且每个操作的结果同步写入该文件。
So you're saying you want a bunch of threads to write data to a single file using a StreamWriter? Easy. Just lock the StreamWriter object.
The code here will create 5 threads. Each thread will perform 5 "actions," and at the end of each action it will write 5 lines to a file named "file."
The result should be a file "file" with 125 lines in it with all "actions" performed concurrently and the result of each action written synchronously to the file.
您那里的代码被巧妙地破坏了 - 特别是,如果排队的工作项首先运行,那么它将在终止之前立即刷新(空)值列表,之后您的工作人员将填充列表(这将最终被忽略)。自动重置事件也不执行任何操作,因为没有任何内容查询或等待其状态。
另外,由于每个线程使用不同锁,这些锁没有任何意义!您需要确保每次访问流写入器时都持有一个共享锁。您不需要在刷新代码和生成代码之间加锁;您只需要确保刷新在生成完成后运行即可。
不过,您可能走在正确的轨道上 - 尽管我会使用固定大小的数组而不是列表,并在数组满时刷新数组中的所有条目。如果线程寿命较长,这可以避免内存不足的可能性。
The code you have there is subtly broken - in particular, if the queued work item runs first, then it will flush the (empty) list of values immediately, before terminating, after which point your worker goes and fills up the List (which will end up being ignored). The auto-reset event also does nothing, since nothing ever queries or waits on its state.
Also, since each thread uses a different lock, the locks have no meaning! You need to make sure you hold a single, shared lock whenever accessing the streamwriter. You don't need a lock between the flushing code and the generation code; you just need to make sure the flush runs after the generation finishes.
You're probably on the right track, though - although I'd use a fixed-size array instead of a list, and flush all entries from the array when it gets full. This avoids the possibility of running out of memory if the thread is long-lived.