Reputation:
I have a non static class with a static method in it and a static variable of type ChangeCkecker
This code (ChangeChecker). 'Changed' takes in a object parameter , checks it against another object return true if the two do not match
public class ChangeChecker
{
public object Backup_Object_Value;
public ChangeChecker() { }
public bool Changed(object obj)
{
bool result = false;
if (!obj.Equals(Backup_Object_Value))
{
Backup_Object_Value = obj;
result = true;
}
return result;
}
}
in my static method in my non-static class 'ClassA' I want to carry out a function only when 'Changed' is true
public class ClassA
{
private static ChangeChecker value = new ChangeChecker();
public static void MethodA(Rect area)
{
if(value.Changed(area))
{
Debug.Log("ChangeMade");
}
}
}
ClassB and ClassC both call classA.MethodA
public class ClassB
{
void Update()
{
ClassA.MethodA(new Rect(2,2,20,20));
}
}
public class ClassC
{
void Update()
{
ClassA.MethodA(new Rect(0,100,100,100));
}
}
What is happening is that because ClassB and ClassC are both calling ClassA.MethodA simultaneously, ClassA ChangeChecker variable gets overridden and the function stops working correctly
Is there a attribute that I can use to mark up the ChangeChecker variable in ClassA so that it is not overridden each time ?
This works perfectly outside of use in static classes and static methods because I would be working with instances of ChangeChecker . Can you help me to get this working correctly ?
Thanks for your time.
Upvotes: 3
Views: 2499
Reputation: 27871
Assuming that ClassB
and ClassC
will run on different threads:
Using lock
or the ThreadStatic
attribute will produce two different behaviors. They are not two options to solve the same problem.
If you use lock
(without ThreadStatic
), the two threads will share the same Backup_Object_Value
object.
Consider this Example:
1) Thread 1 changed value to VALUE1 -> system says value was changed
2) Thread 2 changed value to VALUE2 -> system says value was changed
3) Thread 1 changed value to VALUE1 -> system says value was changed
If you use ThreadStatic
, the two threads will have their own instances of ChangeChecker
and thus Backup_Object_Value
, and the following happens:
1) Thread 1 changed value to VALUE1 -> system says value was changed
2) Thread 2 changed value to VALUE2 -> system says value was changed
3) Thread 1 changed value to VALUE1 -> system says value was NOT changed
Please select which approach you want to use based on the behavior you would like to have.
Assuming that ClassB
and ClassC
will call ClassA.MethodA
from the same thread, then you have the following options:
Option #1:
Make ClassA.MethodA
(and the ChangeChecker value
field) non-static. And then let ClassB
and ClassC
have their own private instances instance of ClassA
, like this:
public class ClassA
{
private ChangeChecker value = new ChangeChecker();
public void MethodA(Rect area)
{
if (value.Changed(area))
{
Debug.Log("ChangeMade");
}
}
}
public class ClassB
{
private readonly ClassA m_ClassAInstance = new ClassA();
void Update()
{
m_ClassAInstance.MethodA(new Rect(2, 2, 20, 20));
}
}
public class ClassC
{
private readonly ClassA m_ClassAInstance = new ClassA();
void Update()
{
m_ClassAInstance.MethodA(new Rect(0, 100, 100, 100));
}
}
Please note that that I am not using Dependency Injection here. This works, but you might want to change this to inject instances of ClassA into ClassB and ClassC.
Option #2:
Note: the first option is the better one. Use this second approach only as a last resort. I wouldn't consider this option a good design.
Add a parameter to ClassA.MethodA
to identify the calling object, and then create a list to hold different values of ChangeChecker
for each object, like this:
public class ObjectAndChangeChecker
{
public WeakReference ObjectReference { get; set; }
public ChangeChecker ChangeChecker { get; set; }
}
public class ClassA
{
private static List<ObjectAndChangeChecker> m_ObjectAndChangeCheckers = new List<ObjectAndChangeChecker>();
private static ChangeChecker GetOrAddChangeChecker(object calling_object)
{
//Get rid of ObjectAndChangeChecker objects than contain garbage collected objects
m_ObjectAndChangeCheckers = m_ObjectAndChangeCheckers.Where(x => x.ObjectReference.IsAlive).ToList();
var object_and_change_checker =
m_ObjectAndChangeCheckers.FirstOrDefault(
x => object.ReferenceEquals(calling_object, x.ObjectReference.Target));
if (object_and_change_checker == null)
{
object_and_change_checker = new ObjectAndChangeChecker()
{
ChangeChecker = new ChangeChecker(),
ObjectReference = new WeakReference(calling_object)
};
m_ObjectAndChangeCheckers.Add(object_and_change_checker);
}
return object_and_change_checker.ChangeChecker;
}
public static void MethodA(Rect area, object calling_object)
{
var value = GetOrAddChangeChecker(calling_object);
if (value.Changed(area))
{
Debug.Log("ChangeMade");
}
}
}
Change ClassB
and ClassC
to pass this
to MethodA
public class ClassB
{
public void Update()
{
ClassA.MethodA(new Rect(2, 2, 20, 20), this);
}
}
public class ClassC
{
public void Update()
{
ClassA.MethodA(new Rect(0, 100, 100, 100), this);
}
}
Please note that in this case, each instance of ClassB or ClassC will have its own ChangeChecker
. If you want to share the same ChangeChecker
for all instances of ClassB for example, then pass this.GetType()
instead of this
. Like this:
public class ClassB
{
public void Update()
{
ClassA.MethodA(new Rect(2, 2, 20, 20), this.GetType());
}
}
Important note: Please make sure that Rect.Equals
actually works as expected.
Upvotes: 0
Reputation: 14687
To avoid the overriding of last changed object value being updated simultaneously; you can enhance your checker class to keep record of the all changes done.
For multithreaded environment you can use the System.Collections.Concurrent.ConcurrentStack. I have tweaked your Checker
to kee all the changes which are being made.
Changed
method new value will be compared to the last change.HasChanges
property can give if there are any previous changes.AcceptChanges
will return all the changes and clear out the history of changes done.And it's threadsafe.
public class ChangeChecker
{
private object Backup_Object_Value;
private readonly object synchronizationToken = new object();
private readonly ConcurrentStack<object> changes = new ConcurrentStack<object>();
public bool HasChanges
{
get
{
return changes.Count > 0;
}
}
public ChangeChecker() { }
public bool Changed(object obj)
{
bool result = false;
lock (synchronizationToken)
{
if (changes.TryPeek(out Backup_Object_Value) && !obj.Equals(Backup_Object_Value))
{
changes.Push(obj);
result = true;
}
}
return result;
}
/// <summary>
/// Returns all changes and clearout the history.
/// </summary>
/// <returns></returns>
public IEnumerable<object> AcceptChanges()
{
lock (synchronizationToken)
{
// keep the last updated of the object, if exists
if (!changes.TryPeek(out Backup_Object_Value))
{
yield break;
}
}
while (changes.Count > 0)
{
object obj;
if (changes.TryPop(out obj))
{
yield return obj;
}
}
}
}
This should give you a better way for change tracking of an object in multithreaded environment. Also there are always chances to improvement so change it as you may.
Upvotes: 0
Reputation: 70746
You can mark the fields with the value ThreadStatic
attribute which will give each thread it's own instance of the variable to play with rather than sharing it with the other thread.
As an alternative solution, I would recommend using lock
:
public bool Changed(object obj)
{
bool result = false;
lock(Backup_Object_Value)
{
if (obj.Equals(Backup_Object_Value)) { }
else
{
Backup_Object_Value = obj;
result = true;
}
}
return result;
}
Which will ensure that multiple threads do not intervene with the same object.
Upvotes: 2
Reputation: 1537
Well you can....
Read on sharing resources in multithreaded applications (hint: use the lock
construct).
or
Mark your static field value
with ThreadStatic
[ThreadStatic]
private static ChangeChecker value = new ChangeChecker();
Which means each thread that accesses it will have its own copy of the field. But if that's the case, maybe you don't need those members to be static at all. But I don't know all your constraints so it's up to you to decide.
Also you could make your code slightly more readable with
if (!obj.Equals(Backup_Object_Value)) {
Backup_Object_Value = obj;
return true;
}
return false;
instead of the empty if
. Also you can get rid of your local bool
variable.
Upvotes: 1
Reputation: 150148
What is happening is that because ClassB and ClassC are both calling ClassA.MethodA simultaneously, ClassA ChangeChecker variable gets overridden and the function stops working correctly
This will only be true if multiple threads are accessing the static method.
If that is not the case, the first call to the static method will complete before the second one begins.
If you are accessing it from multiple threads, you need to include appropriate multithreaded safety. You can start by examining the lock statement.
If you need more information on multi-threaded programming, this is my favorite tutorial.
Upvotes: 2