Привязанность к таргетингу
У меня проблема с пониманием того, как работает параметр AttachedToParent
.
Вот пример кода:
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();
}
Как я понимаю, когда задача имеет дочерние задачи, родительская задача заканчивается, когда все дочерние задачи готовы. Проблема с этим примером заключается в том, что вывод выглядит следующим образом:
0
0
0
Это означает, что родительская задача не ожидала завершения своих дочерних задач. Единственный способ получить достоверный результат 0 1 2
- использовать Wait для всех дочерних Taks, добавив некоторые фрагменты кода, подобные этому, перед оператором return results;
:
Task[] taskList = { t1, t2, t3 };
Task.WaitAll(taskList);
Мой вопрос в том, что. Почему мы используем TaskCreationOptions.AttachedToParent
, когда нам также нужно вручную вызвать метод Wait для каждой дочерней задачи?
Edit:
Пока я писал этот вопрос, я немного изменил код, и теперь AttachedToParent работает хорошо. Единственное различие заключается в том, что я использовал parentTask.Start();
вместо 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();
}
Я до сих пор не понимаю, почему возникает проблема с первым примером.
Ответы
Ответ 1
Посмотрите на это сообщение в блоге: Task.Run vs Task.Factory.StartNew
Первый пример:
Task.Run(someAction);
является упрощенным эквивалентным методу:
Task.Factory.StartNew(someAction,
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default);
Я сделал небольшое исследование, используя рефлектор, вот источник метода 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);
}
Важным параметром метода Task.Factory.StartNew
является TaskCreationOptions creationOptions
. В методе Task.Factory.StartNew
этот параметр равен TaskCreationOptions.DenyChildAttach
. Это означает, что
исключение InvalidOperationException будет выбрано, если будет предпринята попытка приложить дочернюю задачу к созданной задаче
Вам нужно изменить на TaskCreationOptions.None
, чтобы добиться правильного поведения кода.
Метод Task.Run
не позволяет изменять параметр TaskCreationOptions.