kchpchan
kchpchan

Reputation: 87

Thread Safety of StringBuilder in Parallel.For

I have created a struct, say AStruct, and override its ToString() method. Then I have written a parallel for to return some AStruct and put them in a list so I can use a StreamWriter to output them, like

StreamWriter sw = new StreamWriter(@"ABC.txt");
StringBuilder sb = new StringBuilder();
List<AStruct> AList = new List<AStruct>();

Parallel.For(0,10,i =>                          // generate 10 AStruct(s)
{
   AList.Add(DoSomethingThatReturnsAStruct);
});

for(int i =0; i< AList.Count();i++)             //put in a StringBuilder
{
   sb.AppendLine(AList[i].ToString());
}
sw.Write(sb.ToString());
sw.Close();

The problem is the output file only print 7/8 lines of AList, while AList actually got all the 10 elements. I wonder does this relate to thread-safety of StringBuilder. Could someone explain why not all lines are output?

Upvotes: 1

Views: 4220

Answers (1)

user5766999
user5766999

Reputation:

In your code above the instance of StringBuilder is never accessed/modified by anything other than the main thread (or whatever thread is creating the sw) so thread-safety of StringBuilder is not relevant however you have a much bigger bug one that you should always remember when dealing with more than one thread.

Never ever modify a shared resource by multiple threads unless that resource is thread-safe

You are updating the AList from different threads so either use a lock to synchronize access or a thread-safe collection e.g. ConcurrentQueue (order guaranteed) or ConcurrentBag (order not guaranteed)

You are also adding 9 entries to the AList instead of 10.

Finally here's your modified code which yields the expected result.

var sw = new StreamWriter(@"ABC.txt");
try
{        
    var AList = new List<AStruct>();

    var locker = new object();
    Parallel.For(0, 10, i =>                          // generate 10 AStruct(s)
    {
        lock (locker) { AList.Add(new AStruct()); }
    });

    var sb = new StringBuilder();
    for (int i = 0; i < AList.Count; i++)             //put in a StringBuilder
    {
        sb.AppendLine(AList[i].ToString());
    }
    sw.Write(sb.ToString());
} finally
{
    sw.Close();
}

Upvotes: 4

Related Questions