I want to know the consequence of using a class level variable across different functions in a multi-threaded app.
What you're doing in your code will work, sort of, but it doesn't demonstrate the consequence of allowing multiple threads to modify a variable. It doesn't answer your question. It just means that you'll be okay with the particular thing you're doing with this particular variable.
In this case you're just assigning a different reference to your string
variable. That's safe, in a way. You won't get any mangled strings, but it means that you don't know which string a given function will get when it reads the variable. In some scenarios that's not so bad, but it's a little chaotic.
The problem occurs when multiple threads interact with your variable in a way that isn't thread safe.
Here's a really simple test method I wrote without actually knowing what was going to happen:
public class MultithreadedStringTest
{
private string _sharedString;
[TestMethod]
public void DoesntMessUpStrings()
{
var inputStrings = "The quick fox jumped over the lazy brown dog".Split(' ');
var random= new Random();
Parallel.ForEach(Enumerable.Range(0, 1000), x =>
{
_sharedString += " " + inputStrings[random.Next(0, 9)];
});
var outputStrings = _sharedString.Trim().Split(' ');
var nonMangledStrings = outputStrings.Count(s => inputStrings.Contains(s));
Assert.AreEqual(1000, outputStrings.Length,
$"We lost {1000-outputStrings.Length} strings!");
Assert.AreEqual(nonMangledStrings, outputStrings.Length,
$"{outputStrings.Length-nonMangledStrings} strings got mangled.");
}
}
I'm starting with 10 words, and then, in a Parallel.Each
loop appending 1000 words selected from those 10 to a single string from concurrent threads.
I expected the string to get mangled. It didn't. Instead what happened is that out of my 1000 words that I added, typically a few hundred just got lost.
We lost 495 strings!
Obviously that's not the particular operation that you're performing. But what it shows is that when we perform concurrent operations, we need to know that we're either calling a thread safe method or we're using some mechanism to prevent conflicts. We want to know how our code will behave and not cross our fingers and hope for the best.
If we're not careful with it the results will be unpredictable and inconsistent. It might work when you test it and fail later, and when it does it will be difficult to diagnose because you won't be able to see it happen.