Reputation: 591
In the following example when the "Submit" button is clicked the value of the static variable Count is incremented. But is this operation thread safe? Is using Appliation object the proper way of doing such operation? The questions are applicable to Web form application s as well.
The count always seem to increase as I click the Submit button.
View(Razor):
@{
Layout = null;
}
<html>
<body>
<form>
<p>@ViewBag.BeforeCount</p>
<input type="submit" value="Submit" />
</form>
</body>
</html>
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.BeforeCount = StaticVariableTester.Count;
StaticVariableTester.Count += 50;
return View();
}
}
Static Class:
public class StaticVariableTester
{
public static int Count;
}
Upvotes: 12
Views: 1248
Reputation: 3230
If a method (instance or static) only references variables scoped within that method then it is thread safe because each thread has its own stack. You also can achieve thread safety by using any variety of synchronization mechanisms.
This operation is not thread safe because it uses shared variable: ViewBag.BeforeCount.
What Makes a Method Thread-safe? What are the rules?
Upvotes: 4
Reputation: 68660
No, it's not. The += operator is done in 3 steps: read the value of the variable, increase it by one, assign the new value. Expanded:
var count = StaticVariableTester.Count;
count = count + 50;
StaticVariableTester.Count = count;
A thread could be preempted between any two of these steps. This means that if Count
is 0, and two threads execute += 50
concurrently, it's possible Count
will be 50 instead of 100.
T1
reads Count
as 0.T2
reads Count
as 0T1
adds 0 + 50T2
adds 0 + 50T1
assigns 50 to Count
T2
assigns 50 to Count
Count
equals 50Additionally, it could also be preempted between your first two instructions. Which means two concurrent threads might both set ViewBag.BeforeCount
to 0, and only then increment StaticVariableTester.Count
.
Use a lock
private readonly object _countLock = new object();
public ActionResult Index()
{
lock(_countLock)
{
ViewBag.BeforeCount = StaticVariableTester.Count;
StaticVariableTester.Count += 50;
}
return View();
}
Or use Interlocked.Add
public static class StaticVariableTester
{
private static int _count;
public static int Count
{
get { return _count; }
}
public static int IncrementCount(int value)
{
//increments and returns the old value of _count
return Interlocked.Add(ref _count, value) - value;
}
}
public ActionResult Index()
{
ViewBag.BeforeCount = StaticVariableTester.IncrementCount(50);
return View();
}
Upvotes: 13
Reputation: 28107
Increment is not atomic so is not thread safe.
Check out Interlocked.Add
:
Adds two 32-bit integers and replaces the first integer with the sum, as an atomic operation.
You'd use it like this:
Interlocked.Add(ref StaticVariableTester.Count, 50);
Personally I'd wrap this in your StaticVariableTester
class:
public class StaticVariableTester
{
private static int count;
public static void Add(int i)
{
Interlocked.Add(ref count, i);
}
public static int Count
{
get { return count; }
}
}
If you want the returned values (as per dcastro's comment) then you could always do:
public static int AddAndGetNew(int i)
{
return Interlocked.Add(ref count, i);
}
public static int AddAndGetOld(int i)
{
return Interlocked.Add(ref count, i) - i;
}
In your code you could do
ViewBag.BeforeCount = StaticVariableTester.AddAndGetOld(50);
Upvotes: 5