【问题标题】:How can I de-reference an object in C#?如何取消引用 C# 中的对象?
【发布时间】:2014-11-21 20:31:06
【问题描述】:

我有一个在两个索引之间循环的缓冲区,我想在任务中写出当前索引处的对象,并允许程序的其余部分继续处理事情。我试图在保留所有相关部分的同时简化流程。

    object[] buffer = new object[2]
    int currentIndex = 0

    while(true){
        buffer[currentIndex].field1 = newdatahere //data grabbed by sensor bundle
        buffer[currentIndex].field2 = newdatahere //data grabbed by camera bundle
        buffer[currentIndex].field3 = newdatahere //data grabbed from system snapshot

        task.factory.starnew(()=>{
            writeOutObject(buffer[currentIndex])
        }

        buffer[currentIndex] = new object();

        currentIndex = 1 - currentIndex //cycle between the 0 and 1 indices
    }

    void writeOutObject(Object obj){
        //do file IO here
        //write out field1, field2, field3
    }

问题在于,通过将缓冲区项分配给一个新对象,我正在杀死 writeOutObject 方法,因为在任务运行时 obj 不再存在。我希望能够保留旧对象直到它被写出并使缓冲区指向一个新对象。

我想做什么:

    object obj1 = new object();
    obj1.field1 = data1;
    obj1.field2 = data2;
    obj1.field3 = data3;

    obj2 = obj1;
    //de-reference obj1 from the object that it was pointed to and associate it to a new object

    // i want this to write out data1,data2,data3 but instead it is 
    // writing out data4,data5,data6 or some mixture because it has 
    // been overwritten halfway through the file IO
    task.factory.startnew(()=>{ write out obj2 } 

    obj1.field1 = data4;
    obj1.field2 = data5;
    obj1.field3 = data6;

可能是这样的:

    obj1 = new object()

    obj2* = &obj1
    obj1* = &new object

一旦 obj1 被分配,我需要将它的引用中断回 obj2。简单地这样做是行不通的:

    obj1 = new object()
    obj2 = obj1
    obj1 = null // or new object()

根据要求,“真实密码”

    CancellationTokenSource cts = new CancellationTokenSource();
    public void StartMachine()
    {
        Task.Factory.StartNew(() =>
            {
                _isFirstData = true;
                _expiredFlag = false;
                Plc.StartPLC();
                Plc.Start();

                while (true)
                {
                    if (!_paused && !Plc.IsInputStackEmpty() && !Plc.IsOutputSlideOpen())
                    {

                        CameraFront.SnapAquire();

                        // If this is the first data set the wait handles
                        if (!_isFirstData)
                        {
                            CameraBack.SnapAquire();
                        }
                        else
                        {
                            _imageBackRecieved.Set();
                            _databaseInfoRecieved.Set();
                            //_isFirstCard = false;
                        }

                        // Wait for 3 things! Image Front, Image Back, Database
                        bool gotEvents = WaitHandle.WaitAll(_waitHandles, TIMEOUT);
                        if (gotEvents)
                        {
                            if (!_isFirstData)
                            {
                                if (Buffer[1 - NextDataOutIndex].IsDataComplete())
                                {
                                    if (Buffer[1 - NextDataOutIndex].EvaluateData()) 
                                    {
                                        OnPassFailNotification()
                                        Plc.Pass();
                                    }
                                    else
                                    {
                                        OnPassFailNotification()
                                        Plc.Fail();
                                    }
                                }
                                else
                                {
                                    OnPassFailNotification()
                                    Plc.Fail();
                                    Common.Logging
                                }
                            }
                            else
                            {
                                _isFirstData = false;
                            }
                        }
                        else
                        {
                           Common.Logging("WARNING: Wait handle timed out"
                            Plc.Fail();
                        }

                        Data temp = Buffer[1 - NextDataOutIndex];
                        Task.Factory.Startnew(()=>{
                            Data.WriteData(temp);
                        }
                        Buffer[1 - NextDataOutIndex] = new Data(); 

                        // Swap card buffers - alternate between 1 and 0
                        NextdataOutIndex = 1 - NextDataOutIndex;

                        // Do this
                        Plc.WheelAdvance();
                    }
                    else
                    {
                    }
                }
            }, cts.Token);                
    }


    public static void WriteData(Data data)
    {
        if(WRITE_BATCH_FILES)
        try
        {
            if (data.ImageFront != null)
            {
                string filenameforfront = "blahlbah-front.tiff";
                OperatorSet.WriteImage(data.ImageFront, "tiff", 0, filenameforfront);
            }
            if (data.ImageBack != null)
            {
                string filenameforback = "blahblah-back.tiff";
                HOperatorSet.WriteImage(data.ImageBack, "tiff", 0, filenameforback);
            }
        }
        catch (Exception ex)
        {
            Common.Logging.
            //throw ex;
        }

        //TODO: Write out data in xml
        //TODO: Write out metrics
    }

【问题讨论】:

  • “问题在于,通过将缓冲区项分配给一个新对象,我正在杀死 writeOutObject 方法,因为在任务运行时 obj 不再存在” - 不,这不应该发生.我强烈怀疑你误诊了这个问题。一个简短但完整的程序来演示这个问题真的很有帮助。
  • 顺便说一句;我在那里没有看到任何指针。
  • 你们每个人都在用这些对象做某事吗?如果没有,如果您所做的只是将它们写入文件,为什么要这样缓冲它们呢?
  • @JonSkeet 不幸的是,该程序有点过于复杂,无法在此处转录。这些字段实际上是从多个事件处理程序以任意顺序填充的,并且同时填充 0 和 1 索引对象。只有当对象被完全填满时,它才会被写出。澄清一下,并不是该对象不存在,它通常只是随着缓冲区继续循环而被新数据覆盖。将尝试用更清晰的图片更新代码。
  • 我没有要求您提供实际代码 - 我要求提供一个简短但完整的程序来证明问题。您可以从当前代码的副本开始,然后从中删除一些部分,直到剩下的只是一个最小的示例。您很可能会发现,在这样做的过程中,您发现了问题。

标签: c# pointers object reference


【解决方案1】:

就在您 task.factory.StartNew 之前,请执行以下操作

while(...)
{
   ... bunch of other code
   buildTask(buffer[currentIndex]);
   buffer[currentIndex] = new object();
   ... bunch of other code
}

// Within this method, all references to detachedBuffer object will remain pointing to the same
// memory location no matter whether the variable passed in is reassigned.
public void buildTask(object detachedBuffer)
{
    task.factory.starnew(()=>{
        writeOutObject(detachedBuffer);
    };

}

【讨论】:

  • 这绝对比我的建议要好 - 太多时间在嵌入式 C 中工作会让人觉得不好!
  • 不幸的是,这种方法也有同样的问题。只要 buffer[currentIndex] = new object();执行它仍然指向与 detachedBuffer 相同的数据。
  • 如果在启动任务之前重新初始化了detachedBuffer,就会出现这种情况;这在如此小的循环中是可能的。为了使这项工作一直有效,您应该将任务初始化放在它自己的方法中,并将 detachedBuffer 对象作为参数传递给该方法。这将确保任务委托中的 detachedBuffer 语义始终与执行时传递给方法的语义相同。我已经相应地修改了我的答案。
  • 我相信这已经解决了问题。方法调用阻塞,直到任务完全启动并使用它自己对分离缓冲区的引用,然后方法返回并且循环执行的其余部分可以继续。如果我看到任何其他奇怪的东西会更新。
【解决方案2】:

听起来像是 Semaphores 的工作! 信号量是一种线程间通信形式,非常适合这种情况,因为它们允许一个线程锁定信号量,而另一个线程再次释放它。在下面的代码示例中,sem.WaitOne() 行将等到调用 sem.Release() 方法。这会阻塞您的主线程足够长的时间,以便您的任务获得所需的数据。

object[] buffer = new object[2]
int currentIndex = 0

while(true){
    buffer(currentIndex).field1 = newdatahere //data grabbed by sensor bundle
    buffer(currentIndex).field2 = newdatahere //data grabbed by camera bundle
    buffer(currentIndex).field3 = newdatahere //data grabbed from system snapshot

    Semaphore sem = new Semaphore(1,1);  //Initialise the semaphore so that it is checked out

    task.factory.starnew(()=>{
        object item = buffer[currentIndex]; //Create local reference to the data item
        sem.Release(); //Check-in the semaphore (let the WaitOne method return)
        writeOutObject(item)
    }

    sem.WaitOne(); //Block until the semaphore has returned

    buffer[currentIndex] = new object();

    currentIndex = 1 - currentIndex //cycle between the 0 and 1 indices
}

void writeOutObject(Object obj){
    //do file IO here
    //write out field1, field2, field3
}

【讨论】:

  • 您认为为什么需要这样做?我认为根本没有理由跳过锁定。我相信 OP 误诊了这个问题,但我看不出你的建议会有什么帮助。
  • 这只是否定了使用异步块的意义。它会起作用,但 OP 也可以直接调用 writeOutObject。
  • @JonSkeet - 我读它的方式,除非 writeOutObject(buffer[currentIndex]) 在 buffer[currentIndex] = new object(); 之前执行(不能保证,因为它是一项任务)然后 OP 确实会看到他的缓冲区 [currentIndex] 在写入磁盘之前设置为新对象。
  • @PhillipH: 但是如果writeOutObject 使用它已经传递的参数,buffer 与它完全无关。 buffer 中的元素不再具有相同的值并不重要——writeOutObject 方法已经引用了它试图写入的对象。 OP 声称该对象被“杀死”的说法与他们发布的代码毫无意义。
  • OP 似乎有竞争条件。他们启动了一个新线程(TaskFactory.StartNew),它需要一些数据,然后立即更改数据。当writeOutObject(buffer[currentIndex]) 在线程池上执行时,buffer[currentIndex] = new object(); 可能已经被执行了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-12
  • 2019-05-07
  • 1970-01-01
  • 1970-01-01
  • 2016-02-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多