views:

78

answers:

3

I'm trying to implement Tasks im my Application. Here's a sample code:

There's one simple Interface I, 3 classes are derived form it (A,B,C) I create a list of Is, poplualte it with A, B, C instances, and then create a tasf for each other to call method do1();

  interface I
    {
        void do1();

    }

    class A : I
    {
        public void do1()
        {
            Console.WriteLine("A");
        }
    }


    class B : I
    {
        public void do1()
        {
            Console.WriteLine("B");
        }
    }


    class C : I
    {
        public void do1()
        {
            Console.WriteLine("C");
        }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            List<I> l = new List<I>();
            l.Add(new A());
            l.Add(new B());
            l.Add(new C());


            var TaskPool = new List<Task>();


            foreach (var i in l)
            {
                Task task = new Task( () => i.do1()

                    );
                TaskPool.Add(task);
            }


            foreach (var c in TaskPool)
            {
                c.Start();
            }

            Thread.Sleep(3000);
            Console.Read();
        }


    }

I'm expecting to see

A
B
C

in output, but instead of it i get

C
C
C

I kinda found the problem in debugger: all tasks have the same delegate, but i do not know why and how to workaround this. Any Ideas?

Thanks, Ilya.

+7  A: 

This is a very common question. It relates to how "captured variables" work; short version, you need this:

foreach (var i in l)
{
    var copy = i;
    Task task = new Task( () => copy.do1());
    TaskPool.Add(task);
}

The problem here is that i (from the foreach) is technically declared outside the scope of the loop, and thus is captured at that outer scope; you capture the same variable each time (C# captures variables, not *values). Adding copy inside the loop-scope changes this; because of the scope, copy is captured separately per iteration.

Marc Gravell
Thanks for your explaination. Is 'copy' really a copy? As far as i can see, it's just another reference, wright?
portland
@portland: It's a copy of the *value of `i`*, whatever that value is... whether it's a reference or a value type value.
Jon Skeet
+5  A: 

The problem is that you capture the loop variable i when assigning the delegates. If you create a temporary copy before the assignment you can avoid capturing the variable and thus get the values you need.

foreach (var i in l)
{
    var local = i;
    Task task = new Task( () => local.do1());
    TaskPool.Add(task);
}
Brian Rasmussen
+3  A: 

This is the intended behavior for linq expressions. It is somewhat called variable capturing. Refer to this link for some good detailed information on this topic.

In your case, just replace the linq expression with a method group. I mean: this...

Task task = new Task(i.do1);

instead of this...

Task task = new Task(() => i.do1());

EDIT: And one more VERY important thing. You have added the items to the list by adding A, B, C (in that particular order). That does not guarantee that A task will run before B task and so on. You can get anything as the output, ABC, ACB and so on.

Yogesh
+1 Not a very good explanation - but the solution is elegant and it works :)
lasseespeholt
Still very hard to see the difference, according to Marc Gravell♦'s comment
portland
The link I provided has some good information on the topic. I thought the writer explains it in a much better fashion than I can.
Yogesh