views:

98

answers:

5

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?


Code from comment:

    static void Main()
    {
        List<double> Values = new List<double>();
        StreamWriter writer = new StreamWriter("test.out");

        for (int i = 0; i < 1000; i++) Values.Add(i);

        foreach (double V in Values)
        {
            ThreadPool.QueueUserWorkItem(delegate(object state) { SaveValues(writer, V); }, V);
        }
    }

    static void SaveValues(StreamWriter writer, double Value)
    {
        lock (writer) writer.WriteLine(Value);
    } 
+2  A: 

Lock it while accessing it.

lock (myStreamWriter) {
    myStreamWriter.Write(...);
}

That gets rid of the problem of multiple threads trying to access it once. Of course, if you need to further ensure that things get written in the right order from multiple threads you'll have to add extra logic.

Matti Virkkunen
OK , I will try it out I guess
Wajih
OK So I could not ensure that writing, just two values are written, how do i sync. the flow so that I get all the values written?class Test { static void Main() { List<double> Values = new List<double>(); StreamWriter writer = new StreamWriter("test"); for (int i = 0; i < 1000; i++) Values.Add(i); foreach (double V in Values) { ThreadPool.QueueUserWorkItem (delegate (object state) { SaveValues(writer, V); },V); } } static void SaveValues(StreamWriter writer, double Value) { lock(writer) writer.WriteLine(Value); } }
Wajih
@Wajih: not very readable. Please add this to your question.
Henk Holterman
@wajih you should update your question with that. It is too hard to read as a comment.
Foole
+4  A: 

Edit, after decoding the code in your comment

Your problem is not with locking or threads but with capturing the loop variable. This is a classic problem, all your threads run with a 'capture' of the single V variable, and by the time the threads are executed it will have reached its final value of 999. Except maybe for the first few Workitems.

So, instead of :

foreach (double V in Values)
{  
   ThreadPool.QueueUserWorkItem(delegate(object state) 
        { SaveValues(writer, V); }, V); // use of captured V: almost always 999
}

use

foreach (double V in Values)
{
    double W = V;
    ThreadPool.QueueUserWorkItem(delegate(object state)
       { SaveValues(writer, W); }, V);
}

or, a little more concise,

foreach (double V in Values)
{
    double W = V;
    ThreadPool.QueueUserWorkItem((state) => SaveValues(writer, W));
}

As a variation on @Matti's answer, it is recommended to lock on a separate simple object. This reduces the risk of anything else locking on the same object (the StreamWriter code itself for instance).

private StreamWriter writer = ...;         
private Object writerLock = new Object();  // don't expose through a property 
// or any other way

lock (writerLock)
{
    writer.Write(...);
}

But the way you have set up the SaveValues(StreamWriter writer, ...) method make it a little more complicated. It is better to have an object where writer and writerLock are both private members.

Henk Holterman
I tried both of your methods, but it seems that I have to lock the function that has writer.write(...) in it. If i lock as you have suggested, it only saves a single value/
Wajih
@Wajih: read the Edit
Henk Holterman
Ok one of the methods acutally works till 840, i was suppose to write down 1000 values. But seems to be a reasonable approach, though not 100%
Wajih
@Wajih: That's a problem with your Main(). Add `Console.ReadKey(); writer.Close();` as last statements. You're App is closing before the Threads are done.
Henk Holterman
Yup, I guess that was a nice tip
Wajih
+3  A: 

Usually when doing input or output, I prefer a producer/consumer queue with only one consumer that does the actual writing of the output.

Stephen Cleary
+1  A: 

You can synchronize with a lock on the StreamWriter (or a synchronization object that is used when threads want access to the StreamWriter). However, when multiple threads are trying to write, only one will be able to continue.

If this is a problem, you can instead implement a queue, and add a writer thread. The writer thread will dequeue, and write to the StreamWriter. The other threads will enqueue items instead of writing items directly.

You will have to have some form of synchronization for access to this queue. You could simply lock it when each thread accesses it. If you aren't careful, though, could create worse performance problems than you had to begin with.

Edit

It looks like Stephen Cleary has a link to an existing .NET class you can use to implement the queue. I'll leave my answer since it explains why you'd want to do producer/consumer.

Merlyn Morgan-Graham
I guess you are right, a queuing system would be best at this point.
Wajih
+2  A: 

TextWriter.Synchronized

Creates a thread-safe wrapper around the specified TextWriter. ll writes to the returned wrapper will be thread safe.

desco