views:

47

answers:

2

Hi,

I've created an abstract class which implements a method that runs another abstract method on a seperate thread, like this:

 public abstract class ATest
    {
        Thread t;
        protected String status;
        public void Start()
        {
            ThreadStart ts = new ThreadStart(PerformTest);
            t = new Thread(ts);
            t.Start();

        }

        public ATest(String status)
        {
            this.status=status;
        }
        public abstract void PerformTest();
    }

The idea is that classes deriving from ATest only implements the PerformTest method. Thus, any client can just call the Start() method to start the operations within PerformTest() on its own thread. An class deriving from ATest might look like:

class ConcreteTest:ATest
    {
        public ConcreteTest(String status):base(status)
        {

        }

        public override void PerformTest()
        {
            // Do some things...
            // And some more...
            status = "changed";
        }

    }

When creating a ConcreteTest object I want to pass in some object (in the example a String). When PerformTest() runs on its seperate thread the state of this object is to be changed dependent on the outcome of the operations in PerformTest(). An example using the above classes:

class Program
{
    static void Main(string[] args)
    {
        String isPassed = "original";
        ATest test = new ConcreteTest(isPassed);
        test.Start();
        Console.WriteLine(isPassed); // Prints out "original"
    }
}

So I set isPassed to "originial" and passes it to ConcreteTest, which on a separate thread changes to value to "changed". So when i print out isPassed I hoped for the value to be set to "changed", but it turns out it isn't. I guess this have something to do with the value being changed on another thread that the one it was originally created on. Can anyone please explain to me why I get this behavior, and perhaps what I could do to achieve the functionality I'm looking for (that is to change isPassed on the separate thread so that when the thread is done, the Console would print out "changed"?

+1  A: 

Strings are immutable.

When you write status = "changed", you aren't changing any existing string object.
Instead, your are changing the status field to refer to an entirely different String instance.

The status field has nothing to do with the isPassed variable in your Main method. When you write new ConcreteTest(isPassed), you are passing the value of the isPassed variable to the constructor.
The parameter has nothing to do with the variable that you passed to it, except that for now, they happen to refer to the same object. The same is true of your status field.

The simplest way to do this is to define a holder type, like this:

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

If you want to, you can add an implicit cast and a constructor.

You can also do this by exposing the Status property in your base class and writing test.Status in Main.


You also have a less obvious problem.
There is nothing in your code that forces it to wait for the other thread to finish, so your Console.WriteLine might run before the field is assigned by the other thread.

SLaks
"only way"? There are always dozens of ways to do anything in programming. :-)
Sam
I thought that String where treaded as an object and thus was passed by reference rather than by value. Obviously, I was wrong. Your solution fixed my problem. Thank you!
Clean
One thing occurred to me. This works when Holder is generic, but if a don'd define it as generic, it don't work. Why is that?
Clean
A: 

Typically you'll want to store the result in ConcreteTest and when the task is complete read the value out of ConcreteTest, don't try to push it back to the original location. If you really want to push it back, you can do it with a callback and lambda like this.

class ConcreteTest:ATest
{
    public ConcreteTest(Action<string> statusResponder):base(statusResponder)
    {

    }

    public override void PerformTest()
    {
        // Do some things...
        // And some more...
        statusResponder("changed");
    }

}

class Program
{
    static void Main(string[] args)
    {
        String isPassed = "original";
        ATest test = new ConcreteTest(status => isPassed = status);
        test.Start();
        // Need to add a way to wait for task to be done here
        Console.WriteLine(isPassed); // Prints out "original"
    }
}
Sam
Re your comment on my now-deleted answer: Thanks, I'd missed that aspect of the question.
T.J. Crowder
@T.J. Crowder, np, was a good answer, just to a different question. :-)
Sam
I tried this solution, however I got the exact same result. :(
Clean
@Clean, Did you add code to wait for the test to finish where the comment indicates it?
Sam
Yes I did. I put in a Thread.Sleep(2000) to wait fow 2 seconds which should defenitly be sufficient time for the test to finish.
Clean
I know got it to work with this approach as well. Don't ask me why, it just started working. I'm happy :)
Clean