Подтвердить что ты не робот

Привязанность к таргетингу

У меня проблема с пониманием того, как работает параметр 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();
    }

Я до сих пор не понимаю, почему возникает проблема с первым примером.

4b9b3361

Ответ 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.