任务参数,引用类型指向堆上的相同内存地址

时间:2012-11-12 15:26:26

标签: c# c#-4.0 task-parallel-library task reference-type

据我所知,当你使用引用类型作为方法中的参数时,会复制堆栈上的值,因此形式参数指向堆上的相同内存地址因此,一旦完成该方法,更改将保持不变。

这如何处理任务?我刚刚创建了2个新任务并传入了一个在UI线程上声明的数组。其中一个新任务所做的更改立即显示在第二个任务中。当我尝试通过UI线程更改输入(数组)时,相同的参数在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 个答案:

答案 0 :(得分:2)

您创建一个数组:

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

然后将引用副本传递给此数组(当前存储在data中)作为SpecialMethod的参数(通过捕获原始代码,但不重要,它仍然只是一个副本。)

SpecialMethod内,参数int[] historicalData将收到对此原始数组的引用的副本

此后,导致变量data被重新分配的任何内容(与data引用引用的数组中的数据所做的更改相反)对任何<没有影响<由原始引用构成的em>副本 - 它们仍然引用原始数组。

我不清楚你在线程之间传递数据的实际要求是什么,所以我不能提出任何坚定的建议。我通常会尽量避免使用原始数组。

答案 1 :(得分:1)

您的分析在开始时似乎是正确的,但您的结论不是。

您有一个引用类型(数组),并将它传递给一个按值的方法(这是默认值)。这意味着对位于堆上的该数组的引用是复制

由于SpecialMethodMain中的变量都具有相同的引用,因此更改它们引用的值将被两个变量“看到”。

仅当您 mutate 数组时才适用。这就是你用readToEnd做的事情,这就是处理它的代码部分按你的意图工作的原因。

另一方面,data不会改变数组,只需为变量分配一个新数组。这正在改变引用,而不是它引用的对象,这就是你遇到问题的原因。

至于解决方案,有几个。首先,您可以更改代码以改变数组而不是分配新数组;只是改变现有的价值观。如果您需要更改元素数量,请考虑使用List而不是数组。

另一种选择是,不是传递数组,而是添加另一层间接。您可以创建一个具有属性数组的新类,将该类型的对象传递给SpecialMethod,然后您可以更改该对象的属性并在两个位置看到它。您可以使用类似的内容来涵盖一般情况:

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