Thread Safe Properties in C#
I am trying to create thread safe properties in C# and I want to make sure that I am on the correct path - here is what I have done -
private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice
{
get
{
lock (AvgBuyPriceLocker)
{
return _AvgBuyPrice;
}
}
set
{
lock (AvgBuyPriceLocker)
{
_AvgBuyPrice = value;
}
}
}
Reading this posting, it would seem as if this isn't the correct way of doing it -
C# thread safety with get/set
however, this article seems to suggest otherwise,
http://www.codeproject.com/KB/cs/Synchronized.aspx
Does anybody have a more definitive answer?
Edit:
The reason that I want to do the Getter/Setter for this property is b/c I actually want it to fire an event when it is set - so the code would actually be like this -
public class PLTracker
{
public PLEvents Events;
private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice
{
get
{
lock (AvgBuyPriceLocker)
{
return _AvgBuyPrice;
}
}
set
{
lock (AvgBuyPriceLocker)
{
Events.AvgBuyPriceUpdate(value);
_AvgBuyPrice = value;
}
}
}
}
public class PLEvents
{
public delegate void PLUpdateHandler(double Update);
public event PLUpdateHandler AvgBuyPriceUpdateListener;
public void AvgBuyPriceUpdate(double AvgBuyPrice)
{
lock (this)
{
开发者_Python百科 try
{
if (AvgBuyPriceUpdateListener!= null)
{
AvgBuyPriceUpdateListener(AvgBuyPrice);
}
else
{
throw new Exception("AvgBuyPriceUpdateListener is null");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
I am pretty new to making my code thread safe so please feel free to tell me if I am going about it in the totally wrong way!
Will
The locks, as you have written them are pointless. The thread reading the variable, for example, will:
- Acquire the lock.
- Read the value.
- Release the lock.
- Use the read value somehow.
There is nothing to stop another thread from modifying the value after step 3. As variable access in .NET is atomic (see caveat below), the lock is not actually achieving much here: merely adding an overhead. Contrast with the unlocked example:
- Read the value.
- Use the read value somehow.
Another thread may alter the value between step 1 and 2 and this is no different to the locked example.
If you want to ensure state does not change when you are doing some processing, you must read the value and do the processing using that value within the contex of the lock:
- Acquire the lock.
- Read the value.
- Use the read value somehow.
- Release the lock.
Having said that, there are cases when you need to lock when accessing a variable. These are usually due to reasons with the underlying processor: a double
variable cannot be read or written as a single instruction on a 32 bit machine, for example, so you must lock (or use an alternative strategy) to ensure a corrupt value is not read.
Since you have a primitive value this locking will work fine - the issue in the other question was that the property value was a more complex class (a mutable reference type) - the locking will protect accessing and retrieving the instance of the double value held by your class.
If your property value is a mutable reference type on the other hand locking will not protect from changing the class instance once retrieved using its methods, which is what the other poster wanted it to do.
Thread safety is not something you should add to your variables, it is something you should add to your "logic". If you add locks to all your variables, your code will still not necessarily be thread safe, but it will be slow as hell. To write a thread-safe program, Look at your code and decide where multiple threads could be using the same data/objects. Add locks or other safety measures to all those critical places.
For instance, assuming the following bit of pseudo code:
void updateAvgBuyPrice()
{
float oldPrice = AvgBuyPrice;
float newPrice = oldPrice + <Some other logic here>
//Some more new price calculation here
AvgBuyPrice = newPrice;
}
If this code is called from multiple threads at the same time, your locking logic has no use. Imagine thread A getting AvgBuyPrice and doing some calculations. Now before it is done, thread B is also getting the AvgBuyPrice and starting calculations. Thread A in the meantime is done and will assign the new value to AvgBuyPrice. However, just moments later, it will be overwritten by thread B (which still used the old value) and the work of thread A has been lost completely.
So how do you fix this? If we were to use locks (which would be the ugliest and slowest solution, but the easiest if you're just starting with multithreading), we need to put all the logic which changes AvgBuyPrice in locks:
void updateAvgBuyPrice()
{
lock(AvgBuyPriceLocker)
{
float oldPrice = AvgBuyPrice;
float newPrice = oldPrice + <Some other code here>
//Some more new price calculation here
AvgBuyPrice = newPrice;
}
}
Now, if thread B wants to do the calculations while thread A is still busy, it will wait until thread A is done and then do its work using the new value. Keep in mind though, that any other code that also modifies AvgBuyPrice should also lock AvgBuyPriceLocker while it's working!
Still, this will be slow if used often. Locks are expensive and there are a lot of other mechanism to avoid locks, just search for lock-free algorithms.
Reading and writing of doubles is atomic anyway (source) reading and writing of doubles isn't atomic and so it would be necessary to protect access to a double using a lock, however for many types reading and writing is atomic and so the following would be just as safe:
private float AvgBuyPrice
{
get;
set;
}
My point is that thread safety is more complex than simply protecting each of your properties. To give a simple example suppose I have two properties AvgBuyPrice
and StringAvgBuyPrice
:
private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }
And suppose I update the average buy price thusly:
this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();
This clearly isn't thread safe and individually protecting properties in the above way won't help at all. In this case the locking should be performed at a different level rather than at a per-property level.
Although an old question, it gets on top of Google searches, so I add a reply. In you example, the get
locker will not be released after return
. Therefore, I suggest to use the ReaderWriterLockSlim
within a try-finally
block in such a case, which is very well suitable for the result you try to accomplish. It allows multiple threads for reading or exclusive access for writing:
private readonly ReaderWriterLockSlim AvgBuyPriceLocker = new ReaderWriterLockSlim();
private double _AvgBuyPrice = 0;
public double AvgBuyPrice {
get {
AvgBuyPriceLocker.EnterReadLock();
try { return _AvgBuyPrice; }
finally { AvgBuyPriceLocker.ExitReadLock(); }
}
set {
AvgBuyPriceLocker.EnterWriteLock();
try { _AvgBuyPrice = value; }
finally { AvgBuyPriceLocker.ExitWriteLock(); }
}
}
精彩评论