interceptwind
interceptwind

Reputation: 675

How to prevent another thread from modifying a local reference variable in a method while it's running?

How to prevent the reference type variable "local" inside method "test" from changing halfway while it is working? Is there another way other than making a deep copy of it? I also thought that the "lock" should prevent this from happening, but it doesn't in this instance. Can anyone enlighten me?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ConsoleApplication3
{
    class Class1
    {
        public int Value = 0;
    }
    class Test
    {
        private static readonly object m_lock = new object(); 


        static void Main()
        {
            Class1 r1 = new Class1();
            r1.Value = 2;
            Console.WriteLine("MainThread Original Value: {0} ", r1.Value);

            ThreadStart starter = delegate { test(r1); };
            Thread subThread = new Thread(starter);
            subThread.Start();

            Thread.Sleep(1000);
            r1.Value = 1234;
            Console.WriteLine("MainThread New Value: {0} ", r1.Value);
            Thread.Sleep(1000);
            r1.Value = 4321;
            Console.WriteLine("MainThread New Value: {0} ", r1.Value);
            Thread.Sleep(1000);
            r1.Value = 5678;
            Console.WriteLine("MainThread New Value: {0} ", r1.Value);


        }

        static void test(Class1 r)
        {
            lock (m_lock)
            {
                Class1 local = r;
                Console.WriteLine("SubThread Original Values: {0}, {1}", local.Value, r.Value);

                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);

                Console.WriteLine("SubThread Final Values: {0}, {1}", local.Value, r.Value);
                Console.ReadLine();
            }
        }
    }
}

Output:

MainThread Original Value: 2
SubThread Original Values: 2, 2
Working... local value: 2
MainThread New Value: 1234
Working... local value: 1234
Working... local value: 1234
MainThread New Value: 4321
Working... local value: 4321
Working... local value: 4321
MainThread New Value: 5678
Working... local value: 5678
Working... local value: 5678
SubThread Final Values: 5678, 5678

The output I am trying to achieve (where "local" won't be affected by what is going on in the main thread):

MainThread Original Value: 2
SubThread Original Values: 2, 2
Working... local value: 2
MainThread New Value: 1234
Working... local value: 2
Working... local value: 2
MainThread New Value: 4321
Working... local value: 2
Working... local value: 2
MainThread New Value: 5678
Working... local value: 2
Working... local value: 2
SubThread Final Values: 2, 5678

Edit: Assume Class1 cannot be modified, i.e. it is from an external dll.

Upvotes: 1

Views: 196

Answers (1)

Erik Forbes
Erik Forbes

Reputation: 35861

Adding locking won't help here. You've only got one instance of Class1 here, so all threads are accessing that one single object. You'll need to create an instance of Class1 per thread, so that each thread only has its own Class1 instance to play with.

Specifically, the line in test that reads Class1 local = r; is the culprit. If you change that line to read Class1 local = new Class1() then you'll get the behavior you expect.

Alternatively, pass into test a new Class1 instance from Main() in addition to the one you're already passing. That'll do the same thing. If you want to pre-set values before you pass it to the threaded function, do something like this:

static void Main()
    {
        Class1 r1 = new Class1();
        r1.Value = 2;
        Console.WriteLine("MainThread Original Value: {0} ", r1.Value);

        Class1 r2 = new Class1();
        r2.Value = 2;

        ThreadStart starter = delegate { test(r1, r2); };

        *rest of your code here*

And for static void test(Class1 r) instead use

static void test(Class1 tr, Class1 r) {
    Class1 local = r;

    *etc*

Note here that I'm passing r2 instead of r1 to test.

Here is your code, fixed to present the same output as what you're looking for:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ConsoleApplication3 {
    class Class1 {
        public int Value = 0;
    }
    class Test {
        private static readonly object m_lock = new object();


        static void Main() {
            Class1 r1 = new Class1();
            r1.Value = 2;

            Class1 r2 = new Class1();
            r2.Value = 2;

            Console.WriteLine("MainThread Original Value: {0} ", r1.Value);

            ThreadStart starter = delegate { test(r1, r2); };
            Thread subThread = new Thread(starter);
            subThread.Start();

            Thread.Sleep(1000);
            r1.Value = 1234;
            Console.WriteLine("MainThread New Value: {0} ", r1.Value);
            Thread.Sleep(1000);
            r1.Value = 4321;
            Console.WriteLine("MainThread New Value: {0} ", r1.Value);
            Thread.Sleep(1000);
            r1.Value = 5678;
            Console.WriteLine("MainThread New Value: {0} ", r1.Value);


        }

        static void test(Class1 r, Class1 tr) {
            lock (m_lock) {
                Class1 local = tr;
                Console.WriteLine("SubThread Original Values: {0}, {1}", local.Value, r.Value);

                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);
                Console.WriteLine("Working... local value: {0}", local.Value);
                Thread.Sleep(500);

                Console.WriteLine("SubThread Final Values: {0}, {1}", local.Value, r.Value);
                Console.ReadLine();
            }
        }
    }
}

And the output:

MainThread Original Value: 2
SubThread Original Values: 2, 2
Working... local value: 2
MainThread New Value: 1234
Working... local value: 2
Working... local value: 2
MainThread New Value: 4321
Working... local value: 2
Working... local value: 2
MainThread New Value: 5678
Working... local value: 2
Working... local value: 2
SubThread Final Values: 2, 5678

Upvotes: 1

Related Questions