views:

769

answers:

3

I have an object that I am accessing from two threads. One thread calls a long-running member function on the object that returns a value. The second thread updates the object used to produce that value.

I if I call Interlock.Exchange to replace the object from the second thread while the first thread is executing: 1. Will the old thread's self retain a reference to the original object. 2. Is there a risk that the original object is garbage collected?

import System;
import System.Threading;
import System.Generics;

class Example {
    var mData = new String("Old");
    public void LongFunction() {
        Thread.Sleep(1000);
        Console.WriteLine(mData);
    }
    public void Update() {
         Interlocked.Exchange(ref mData, "Old");
    }
}

class Program { 
   public static Main(string[] argv) {
       var e = new Example();
       var t = new Thread(new ThreadStart(e.LongFunction()));
       t.Start();
       e.Update();
    }
}

Is this guaranteed to always print "Old"? Thanks.

+2  A: 

There is no risk for the object to be garbage collected because it is still referenced in the old thread's call stack.

Edit: From your code, mData is initialized with "Old" and Update() overwrites it with "Old", so indeed it will always print "Old".

If you meant:

public void Update()
{
    Interlocked.Exchange(ref mData, "New");
}

Then the printed result can be either "New" or "Old", but will most likely be "New" since you wait 1 second before printing the value.

I'm not sure I understand the relation between your code sample and your garbage collection concerns.

SelflessCoder
A: 

Except for you forgetting to call t.Start(), yes.

You never have to worry about a variable being garbage collected from under you. If you can ever get a reference to an object, it won't have been garbage collected.

Alun Harford
A: 

Your question seems to be one of the pitfalls of using Interlocked.Exchange on object types in C#. The garbage collector isn't the place too look for your troubles.

First, remember, your replacing the memory directly. If your the type of your mData variable is Disposable, only the newest copy can be disposed. Interlocked breaks object finalization.

More concerning is if you are calling a member function on the mData object which takes a long time to execute. This changes the value of self for the member function while it is executing.

Here is some code that shows the problem with using Exchange on an object: using System; using System.Collections.Generic; using System.Threading;

namespace ConcurrentTest
{
    class ValType : IDisposable
    {
        public int I { get; set; }
        public int Mem()
        {
            Thread.Sleep(1000);
            return I;
        }


        void IDisposable.Dispose()
        {
            Console.WriteLine("Destroying");
        }
    }

    class Example
    {
        ValType mData = new ValType {I = 0};
        public void Print()
        {
            Console.WriteLine(mData.I);
            Thread.Sleep(2000);
            Console.WriteLine(mData.Mem());
        }

        public void Update()
        {    
            var data = new ValType() { I = 1 };
            Interlocked.Exchange(ref mData, null);
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var e = new Example();
            var t = new Thread(new ThreadStart(e.Print));
            t.Start();
            e.Update();
            t.Join();
            Console.ReadKey(false);
        }
    }
}
None
You are confusing Dispose and finalizer. Dispose doesn't get called explicitly by the garbage collector, the finalizer does. Interlocked.Exchange does not break object finalization... and your code throws NullException.
SelflessCoder