1

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.

interceptwind
  • 665
  • 4
  • 14

1 Answers1

1

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
Erik Forbes
  • 35,357
  • 27
  • 98
  • 122
  • In that case, how can I pass the value of r1 from the main thread to the 2nd thread at the instant i start the 2nd thread? – interceptwind May 28 '14 at 06:42
  • When you write ``Class1 local = r;`` you're basically telling ``local`` to refer to the same exact instance as whatever reference you passed in as ``r``. – Erik Forbes May 28 '14 at 06:46
  • if i use this "Class1 local = new Class1() ", I am not using the parameter, am I? – interceptwind May 28 '14 at 06:48
  • Perhaps your question is too vague - what is your actual situation? From the example and the output you're seeking, both of these methods will do the job. – Erik Forbes May 28 '14 at 06:51
  • but you are right. I guess adding locking here won't help. – interceptwind May 28 '14 at 06:52
  • If you really need ``r1`` in the ``test`` method, you could pass both ``r1`` and ``r2`` from Main. – Erik Forbes May 28 '14 at 06:54
  • I want to 1) pass a reference variable from main thread to 2nd thread 2) the "local" variable in the 2nd thread to stay the same through out its life time in the 2nd thread, no matter what happened in the main thread – interceptwind May 28 '14 at 06:55
  • You can't do that. In your code, Main.r1, test.r, and test.local all refer to the same object. – Erik Forbes May 28 '14 at 06:57
  • thus the only way is to do a deep copy of test.r in the 2nd thread? – interceptwind May 28 '14 at 07:00
  • Yeah, pretty much. This is how reference types work - they are passed 'By Reference'. 'r' and 'local' in test all *refer* to 'r1' from Main. – Erik Forbes May 28 '14 at 07:03
  • i have the nagging suspicion that this is what's going on (I have only a vague concept of reference type previously). Thanks for confirmation! :) – interceptwind May 28 '14 at 07:16
  • No worries. =) Read here for a better understanding: http://www.albahari.com/valuevsreftypes.aspx – Erik Forbes May 28 '14 at 07:19