user4740550
user4740550

Reputation:

In a non static class ,How do I retain the value of a static variable used in a static method called in multiple different classes simultaneously

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

Answers (5)

Yacoub Massad
Yacoub Massad

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

vendettamit
vendettamit

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.

  • with 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

Darren
Darren

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

async
async

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

Eric J.
Eric J.

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

Related Questions