Параметры задачи, указывают ли типы ссылок на один и тот же адрес памяти в куче

Насколько я понял, когда вы используете ссылочный тип в качестве параметра в методе, значение в стеке копируется, и поэтому формальный параметр указывает на тот же адрес памяти в куче, что и оригинал, следовательно, изменяется сохраняются, как только вы закончили с методом.

Как это работает с задачами? Я только что создал 2 новые задачи и передал в массив, который был объявлен в потоке пользовательского интерфейса. Изменения, внесенные в одно из новых заданий, сразу же были показаны во втором задании. Когда я пытаюсь изменить вход (массив) через поток пользовательского интерфейса, тот же параметр не изменился в 2 новых задачах. У меня сложилось впечатление, что так должно быть, поскольку все они должны указывать на одну и ту же область памяти в куче.

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

namespace TasksAndMemory
{
    class Program
    {
        private static ManualResetEvent mre = new ManualResetEvent(false);

        static void Main(string[] args)
        {
            int[] readToEnd = new int[2];
            int[] data = new int[] { 1, 2, 3, 4, 5, 6 };
            int valueType = 5;


            int pageCounter = 1;


            Task[] tasks = new Task[2];


            for (int x = 1; x < 3; x++)
            {
                //Needed due to closure problem
                int i = x;

                tasks[i-1] = Task.Factory.StartNew(() =>
                {
                    SpecialMethod(data, readToEnd, i, valueType);
                });
            }

            while(pageCounter < 4)
            {
                if (readToEnd[0] == 1 && readToEnd[1] == 1)
                {
                    //Sets the state of the event to nonsignaled, causing threads to block
                    mre.Reset();
                    int[] temp = new int[] { 7, 8, 9, 10, 11, 12 };
                    data = temp;
                    readToEnd[0] = 0;
                    readToEnd[1] = 0;

                    //Sets the state of the event to signaled, allowing one or more waiting threads to proceed.
                    mre.Set();
                    pageCounter++;
                }
            }
            Console.ReadLine();
        }

        public static void SpecialMethod(int[] historicalData, int[] readToEnd, int taskNumber, int valueTy)
        {
            int[] temp = new int[] { 100, 200, 300, 400, 500, 600 };

            for (int x = 0; x <= historicalData.Length; x++)
            {
                if (x == historicalData.Length)
                {
                    readToEnd[taskNumber-1] = 1;
                    mre.WaitOne();
                    x = 0;
                 }
                else
                {
                    valueTy++;
                    temp[x] = temp[x] + taskNumber;
                }
            }
        }
    }
}

2 ответа

Решение

Ваш анализ кажется правильным с самого начала, но ваши выводы не так.

У вас есть ссылочный тип (массив), и вы передаете его методу по значению (по умолчанию). Это означает, что ссылка на этот массив, который находится в куче, копируется.

Потому что обе переменные в SpecialMethod а также Main имеют одинаковые ссылки, изменяя значение, на которое они ссылаются, будут "видны" обеими переменными.

Это применимо только если вы изменяете массив. Это то, что вы делаете с readToEndВот почему разделы кода, связанные с ним, работают так, как вы и предполагали.

С data с другой стороны, вы не изменяете массив, вы просто назначаете новый массив переменной. Это меняет ссылку, а не объект, на который она ссылается, и именно поэтому у вас возникают проблемы.

Что касается решений, то их несколько. Во-первых, вы можете изменить код, чтобы изменить массив, а не назначать новый; просто измените существующие значения. Если вам нужно изменить количество элементов, рассмотрите возможность использования List а не массив.

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

public class Wrapper<T>
{
    public T Value { get; set; }
}

Вы создаете массив:

int[] data = new int[] { 1, 2, 3, 4, 5, 6 };

Затем вы передаете копию вашей ссылки в этот массив (в настоящее время хранится в data) в качестве параметра SpecialMethod (через захват в вашем исходном коде, но не важно, это все еще просто копия).

В SpecialMethodпараметр int[] historicalData получит копию ссылки на этот оригинальный массив.

После этого все, что вызывает переменную data для переназначения (в отличие от изменений, внесенных в данные в массиве, на который ссылается data) не влияет на любые копии, которые были сделаны из его исходной ссылки - они все еще ссылаются на исходный массив.

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

Другие вопросы по тегам