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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With