Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AttachedToParent Task confusion

I have a problem understanding how AttachedToParent parameter works.

Here is the sample code:

public static void Main(string[] args)
    {
        Task<int[]> parentTask = Task.Run(()=> 
        {
            int[] results = new int[3];

            Task t1 = new Task(() => { Thread.Sleep(3000); results[0] = 0; }, TaskCreationOptions.AttachedToParent);
            Task t2 = new Task(() => { Thread.Sleep(3000); results[1] = 1; }, TaskCreationOptions.AttachedToParent);
            Task t3 = new Task(() => { Thread.Sleep(3000); results[2] = 2; }, TaskCreationOptions.AttachedToParent);

            t1.Start();
            t2.Start();
            t3.Start();

            return results;
        });

        Task finalTask = parentTask.ContinueWith(parent =>
        {
            foreach (int result in parent.Result)
            {
                Console.WriteLine(result);
            }
        });

        finalTask.Wait();
        Console.ReadLine();
    }

As I understand, when a Task has child Tasks, the parent Task finishes when all the child tasks are ready. The problem with this example is that the output looks like this:

0
0
0

This means that the parent Task was not waiting for its child tasks to finish. The only way to get a valid result 0 1 2 is to use Wait on all of the child Taks, by adding some piece of code like this before the return results; statement:

Task[] taskList = { t1, t2, t3 };
Task.WaitAll(taskList);

My question is this. Why do we use TaskCreationOptions.AttachedToParent when we also have to manually call Wait method for every child Task?

Edit:

While I was writing this question, I've changed code a little bit and now AttachedToParent works well. The only difference is that I've used parentTask.Start(); instead of the Task.Run();.

public static void Main(string[] args)
    {
        Task<int[]> parentTask = new Task<int[]>(()=> 
        {
            int[] results = new int[3];

            Task t1 = new Task(() => { Thread.Sleep(3000); results[0] = 0; }, TaskCreationOptions.AttachedToParent);
            Task t2 = new Task(() => { Thread.Sleep(3000); results[1] = 1; }, TaskCreationOptions.AttachedToParent);
            Task t3 = new Task(() => { Thread.Sleep(3000); results[2] = 2; }, TaskCreationOptions.AttachedToParent);

            t1.Start();
            t2.Start();
            t3.Start();

            //Task[] taskList = { t1, t2, t3 };
            //Task.WaitAll(taskList);

            return results;
        });

        parentTask.Start();

        Task finalTask = parentTask.ContinueWith(parent =>
        {
            foreach (int result in parent.Result)
            {
                Console.WriteLine(result);
            }
        });

        finalTask.Wait();
        Console.ReadLine();
    }

I still don't understand why there is a problem with the first example.

like image 902
milan-j Avatar asked Nov 29 '13 05:11

milan-j


1 Answers

Look at this blog post: Task.Run vs Task.Factory.StartNew

The first example:

Task.Run(someAction);

is simplified equivalent of method:

Task.Factory.StartNew(someAction,
         CancellationToken.None,
         TaskCreationOptions.DenyChildAttach,
         TaskScheduler.Default);

I made little research, using reflector, here is a source of method Task.Run

public static Task Run(Func<Task> function, CancellationToken cancellationToken)
    {
      if (function == null)
        throw new ArgumentNullException("function");
      cancellationToken.ThrowIfSourceDisposed();
      if (cancellationToken.IsCancellationRequested)
        return Task.FromCancellation(cancellationToken);
      else
        return (Task) new UnwrapPromise<VoidTaskResult>(
            (Task) Task<Task>.Factory.StartNew(function,
                                  cancellationToken, 
                                  TaskCreationOptions.DenyChildAttach,
                                  TaskScheduler.Default),
            true);
    }

The important parameter of method Task.Factory.StartNew is TaskCreationOptions creationOptions. In method Task.Factory.StartNew that parameter equals TaskCreationOptions.DenyChildAttach. It mean that

an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task

You need to change to TaskCreationOptions.None to achieve right behavior of code.

Method Task.Run does not provide ability to change TaskCreationOptions parameter.

like image 151
testCoder Avatar answered Oct 09 '22 07:10

testCoder