Ask a questionAsk a question
 

AnswerDifferent output of 2 seemingly same code snippet

  • Wednesday, October 14, 2009 12:28 PMHemant Jangid Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    Consider following console application:

        class Program
        {
            static void Main (string[] args) {
                string targetPath = @"D:\Test";
    
                var files = Directory.GetFiles (targetPath);
    
                //Parallel method
                Parallel.ForEach (files, f => Console.WriteLine (f));
    
                //Classic method
                foreach (string f in files)
                    Task.Factory.StartNew (() => Console.WriteLine (f));
    
                Console.ReadLine ();
            }
        }
    
    Assume that I have four files in "D:\Test" folder: "1.txt", "2.txt", "3.txt", "4.txt".

    When I run about program, first method i.e. Parallel method prints (correctly):
    D:\Test\1.txt
    D:\Test\2.txt
    D:\Test\3.txt
    D:\Test\4.txt

    But the classic method prints:
    D:\Test\4.txt
    D:\Test\4.txt
    D:\Test\4.txt
    D:\Test\4.txt

    Why do classic way doesn't work whereas Parallel one does?

    P.S.: Please let me know if you need me to explain why do I need to use classic foreach.

Answers

  • Wednesday, October 14, 2009 3:24 PMAndy Clymer Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     AnswerHas Code
    You are falling fowl of the typical anonymous methods state capture..The object created for the anonymous method is bound to the loop variable, when this executes asynchronously you get the value of the loop variable at the given point in time when the Console.WriteLine executes.

    If you wish to use the value as is at the point of task creation you need to capture  a local variable of the loop.

    simply add 

      //Classic method
                foreach (string f in files)
    		{
    			string filename = f;
                    Task.Factory.StartNew (() => Console.WriteLine (filename));
    
    		
    		}

All Replies

  • Wednesday, October 14, 2009 12:48 PMHemant Jangid Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    I thought all tasks launched by classic foreach are referencing same string. So I tried with different instances:

    //Classic method
    foreach (string f in files)
        Task.Factory.StartNew (() => Console.WriteLine (String.Copy (f)));
    
    
    But even this doesn't work. I get same output:

    D:\Test\4.txt
    D:\Test\4.txt
    D:\Test\4.txt
    D:\Test\4.txt
  • Wednesday, October 14, 2009 3:24 PMAndy Clymer Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     AnswerHas Code
    You are falling fowl of the typical anonymous methods state capture..The object created for the anonymous method is bound to the loop variable, when this executes asynchronously you get the value of the loop variable at the given point in time when the Console.WriteLine executes.

    If you wish to use the value as is at the point of task creation you need to capture  a local variable of the loop.

    simply add 

      //Classic method
                foreach (string f in files)
    		{
    			string filename = f;
                    Task.Factory.StartNew (() => Console.WriteLine (filename));
    
    		
    		}

  • Thursday, October 15, 2009 4:43 AMHemant Jangid Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Thanks for the answer. It worked.

    I must say this is the first time I have touched a dark corner of C#. Most of the time it works as you expect it to.