Как запретить другому потоку изменять локальную ссылочную переменную в методе во время его работы?

1

Как предотвратить замену переменной ссылочного типа "локальный" внутри метода "тест" на полпути во время работы? Есть ли другой способ, кроме глубокой копии? Я также думал, что "блокировка" должна помешать этому, но в этом случае этого не происходит. Может ли кто-нибудь просветить меня?

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();
            }
        }
    }
}

Вывод:

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

Результат, который я пытаюсь достичь (где "локальный" не будет затронут тем, что происходит в основном потоке):

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

Изменение: Предположите, что Class1 не может быть изменен, т.е. Из внешней dll.

Теги:
multithreading
thread-safety

1 ответ

1

Добавление блокировки здесь не поможет. У вас есть только один экземпляр класса 1, поэтому все потоки обращаются к этому одному объекту. Вам нужно будет создать экземпляр класса 1 для каждого потока, чтобы каждый поток имел свой собственный экземпляр Class1 для воспроизведения.

В частности, строка в test которая читает Class1 local = r; является виновником. Если вы измените эту строку, чтобы прочитать Class1 local = new Class1() вы получите ожидаемое поведение.

В качестве альтернативы, перейдите в test нового экземпляра Class1 из Main() в дополнение к тому, который вы уже проходите. Это сделает то же самое. Если вы хотите предварительно установить значения перед тем, как передать их в поточную функцию, выполните следующие действия:

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*

А для static void test(Class1 r) вместо этого используйте

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

    *etc*

Обратите внимание, что я передаю r2 вместо r1 для test.

Вот ваш код, исправленный, чтобы представить тот же результат, что и то, что вы ищете:

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();
            }
        }
    }
}

И выход:

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
  • 0
    В таком случае, как я могу передать значение r1 из основного потока во второй поток в тот момент, когда я запускаю второй поток?
  • 0
    Вы уже делаете это, передавая «r1» в качестве параметра.
Показать ещё 12 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню