Почему RunContinuationsAsynchronous является недопустимым параметром продолжения для TaskFactory

Я попытался создать экземпляр TaskFactory с помощью TaskContinuationOptions.RunContinuationsAsynchronously, чтобы быть уверенным, что созданные задачи будут выполняться асинхронно. Однако это кажется невозможным, поскольку я получаю ArgumentOutOfRangeException.

TaskFactory tf = new TaskFactory(TaskCreationOptions.None, 
    TaskContinuationOptions.RunContinuationsAsynchronously);
//ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'continuationOptions')

Теперь я думаю, что то, чего я хотел достичь, достигается использованием TaskCreationOptions.RunContinuationsAsynchronously, а не TaskContinuationOptions.RunContinuationsAsynchronously. Это в сторону. Мне не удалось найти список недопустимых параметров или причину их недопустимости в документации.

Итак, почему TaskContinuationOptions.RunContinuationsAsynchronously недопустимый вариант для TaskFactory?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это было не так очевидно, как я ожидал.

Первое, что важно знать, это то, что перечисление TaskCreationOptions неофициально (не задокументировано, кроме как в коде, трудно себе представить изменение) подмножеством TaskContinuationOptions согласно этому комментарию в источнике:

/// <summary>
/// Specifies flags that control optional behavior for the creation and execution of tasks.
/// </summary>
// NOTE: These options are a subset of TaskContinuationsOptions, thus before adding a flag check it is
// not already in use.

Моя сильная гипотеза состоит в том, что для поддержания этой битовой совместимости RunContinuationsAsynchronously делается частью TaskContinuationsOptions, хотя на самом деле он используется только как TaskCreationOptions.

Например, когда мы используем Task.ContinueWith, мы передаем TaskContinuationsOptions, которые используются одновременно для двух целей:

  1. Подмножество TaskCreationOptions будет извлечено и использовано при построении ContinuationTaskFromTask
  2. Остальные параметры передаются в конструкцию ContinueWithTaskContinuation, которая инкапсулирует их вместе с ContinuationTaskFromTask.

Это сжатая версия исходного кода без проверки исключений:

private Task ContinueWith(Action<Task> continuationAction, TaskScheduler scheduler,
    CancellationToken cancellationToken, TaskContinuationOptions continuationOptions) {

    // OP Comment: 1 converison to TaskCreationOptions    
    CreationOptionsFromContinuationOptions(continuationOptions, out TaskCreationOptions creationOptions, out InternalTaskOptions internalOptions);

    Task continuationTask = new ContinuationTaskFromTask(
        this, continuationAction, null,
        creationOptions, internalOptions
    );

    // OP Comment: 2. creating the continuation which makes use of the ContinuationOptions that are just relevant for continuation
    ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
    // OP COMMENT: this calls inside new ContinueWithTaskContinuation(continuationTask, options, scheduler);

    return continuationTask;
}

Исключение, с которым я столкнулся, было в конструкторе TaskFactory, который допускает ОБА TaskCreationOptions и TaskContinuationOptions во всех своих перегрузках, поэтому последний аргумент рассматривается как «чистый» TaskContinuationOptions, поскольку пользователь может передать TaskCreationOptions отдельно.

Чтобы понять, почему это «не имеет смысла» (согласно комментариям в коде, который выдает исключение из вопроса), нам нужно посмотреть, как обычно вызываются продолжения.

Task имеет RunContinuationsметод, в котором исходный объект задачи определяет, запретить ли синхронное выполнение ее продолжений. (m_stateflags включает биты TaskCreationOptions, использованные для создания задачи)

bool canInlineContinuations =
           (m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) == 0 &&
           RuntimeHelpers.TryEnsureSufficientExecutionStack();

Потом звонит TaskContinuation.Run

void Run(Task completedTask, bool canInlineContinuationTask)

Здесь становится актуальным TaskContinuationOptions объекта продолжения задачи, но роль RunContinuationsAsynchronously уже выполнена (как TaskCreationOptions, а не TaskContinuationOptions), помогая определить значение аргумента canInlineContinuationTask.

Кроме того, согласно многочисленным комментариям Панайотиса Канавоса, значение TaskContinuationOptions.None уже официально определило асинхронный случай для TaskContinuationOptions:

Продолжение выполняется асинхронно, когда предшествующая задача завершается независимо от окончательного значения свойства Status предшествующего объекта.

Так зачем же добавлять лишнюю опцию, если не утверждать, что TaskContinuationOptions неофициально является расширенным набором TaskCreationOptions и может служить и тем, и другим в сигнатурах методов, как показано выше для ContinueWith?!

И наконец, асинхронный/синхронный не означает сопоставление 1 с 1 с «в другом потоке», «том же потоке». Как прокомментировал Панайотис Канавос, мы зависим от конкретного TaskScheduler (с которым связана задача) в том, будет ли продолжение в том же потоке или нет (например, в случае однопоточной реализации TaskScheduler)

Таким образом, асинхронный означает — поставленный в очередь, синхронный — выполняемый в режиме онлайн. Это ясно видно по названиям, использованным в последнем описанном методе ContinueWithTaskContinuation.Run.

// Either run directly or just queue it up for execution, depending
// on whether synchronous or asynchronous execution is wanted.
if (canInlineContinuationTask && // inlining is allowed by the caller
    (options & TaskContinuationOptions.ExecuteSynchronously) != 0) // synchronous execution was requested by the continuation's creator
{
    InlineIfPossibleOrElseQueue(continuationTask, needsProtection: true);
} else {
    try { continuationTask.ScheduleAndStart(needsProtection: true); } catch (TaskSchedulerException) {
        // No further action is necessary -- ScheduleAndStart() already transitioned the
        // task to faulted.  But we want to make sure that no exception is thrown from here.
    }
}

Не совсем. TaskFactory создает только новые объекты задач либо как новые задачи, либо как продолжение нескольких задач. Он не планирует их выполнение. Вы можете указать любой флаг, но если планировщик однопоточный, все они будут выполняться в одном потоке. Если вы проверите код в TaskFactory.cs, вы увидите, что эти параметры продолжения используются только для ContinueWhenAny и ContinueWhenAll.

Panagiotis Kanavos 21.06.2024 08:33

@PanagiotisKanavos «Он не планирует их выполнение», а через StartNew… еще раз спасибо за спам.

Ivan Petrov 21.06.2024 08:35

В документации ContinueWhenAll упоминаются некоторые недопустимые параметры, но я подозреваю, что никто не ожидал, что кто-то укажет параметр по умолчанию, фактически не используя значение по умолчанию, None. Эти методы предназначены для сложных сценариев и часто не документируются так подробно.

Panagiotis Kanavos 21.06.2024 08:36

Это не спам. На самом деле я использовал все это для асинхронных конвейеров до 2012 года, до выхода Dataflow. Вот почему я знаю, где находится код и статьи. Я мог бы проголосовать за закрытие этого очевидно основанного на мнении вопроса или проголосовать против этого явно неправильного ответа, но я подозреваю, что это проблема XY, а реальная проблема X стоит того, чтобы ее решить.

Panagiotis Kanavos 21.06.2024 08:36

Единственный способ ответить на ваш вопрос — посмотреть на код, вину Git, проблемы с Github и, возможно, задать вопрос самому Стивену Таубу. Люди задавали подобные вопросы десять лет назад, и он ответил в Почему TaskContinuationsOptions.ExecuteSynchronous согласен?: For a relatively advanced feature, I’ve been surprised how often this question has come up recently.

Panagiotis Kanavos 21.06.2024 08:40

Тот факт, что этот конструктор не документирован полностью, ясно указывает на то, что он предназначен для сложных сценариев. В противном случае за последние 14 лет многие люди спрашивали бы об этом достаточно, чтобы упомянуть об этом в документах.

Panagiotis Kanavos 21.06.2024 08:41

@PanagiotisKanavos, ты на самом деле не читал мой ответ и не смотрел источник, на который я ссылался. Главный вывод заключается в том, что TaskContinuationOptions является расширенным набором TaskCreationOptions, и это единственная причина, по которой RunContinuationsAsynchronoly является его частью (который, кстати, был представлен в .NET Framework 4.6) 2015 года или около того, возможно, вы застряли в 2012 году.

Ivan Petrov 21.06.2024 08:46
it does via StartNew. неправильно, и это тщательно задокументировано. Проверьте метод , вы увидите, что он в конечном итоге создает Task, который в конечном итоге ставит себя в очередь к планировщику
Panagiotis Kanavos 21.06.2024 08:47

Наоборот, я не только смотрел на него со вчерашнего дня, но и следил за реальными звонками. Я должен сказать, что вы не просматривали эти ссылки, иначе вы бы увидели, что они из тех же файлов, которые вы просматривали.

Panagiotis Kanavos 21.06.2024 08:48

@PanagiotisKanavos перечитал мой последний комментарий. Также посмотрите этот пост от 2015 года devblogs.microsoft.com/pfxteam/new-task-apis-in-net-4-6, документирующий, когда параметр был добавлен в перечисления.

Ivan Petrov 21.06.2024 08:48

Итак, вы не согласны с реальным исходным кодом Task.cs, который вызывает m_taskScheduler.InternalQueueTask(this); ?

Panagiotis Kanavos 21.06.2024 08:49

@PanagiotisKanavos, никаких рассуждений, только утверждения? Что не так? Я до сих пор не видел соответствующего комментария, в котором обсуждались бы вещи, которые я написал, особенно тот факт, что одно является подмножеством другого и существует четкий код того, как оно преобразуется, и что оно служит двойной цели.

Ivan Petrov 21.06.2024 08:50

@PanagiotisKanavos какое отношение это имеет (то есть не к спаму) к моему ответу?

Ivan Petrov 21.06.2024 08:50

Вы утверждали it does via StartNew.. thanks for the spam again. Это просто неправильно (включая определение спама). Я все еще думаю, что у вас другая проблема, и предполагаю, что вы можете решить ее с помощью слишком сложного кода, а опытные разработчики помогут вам найти простое решение. Однако на данный момент, я думаю, вам следует выяснить это самостоятельно. Мне жаль, что я потратил свое время

Panagiotis Kanavos 21.06.2024 08:54

@PanagiotisKanavos, ты не прокомментировал ни одну вещь, которую я написал в ответе, то есть спам. Затем вы заявили, что TaskFactory просто создает новые объекты, не планируя их, что, если бы вы прочитали ответ, не имеет значения, то есть спам. Но это также НЕПРАВИЛЬНО, как вы можете видеть source.dot.net/#System.Private.CoreLib/src/libraries/…: особенно по этому комментарию // Create and schedule the task. This throws an InvalidOperationException if already shut down.

Ivan Petrov 21.06.2024 09:00

Вы репостите мои ссылки. Теперь прочитайте 14 строк ниже, под номером 1155, и посмотрите, что делает метод.

Panagiotis Kanavos 21.06.2024 09:03

@PanagiotisKanavos, ты имеешь в виду t.ScheduleAndStart(false);, который определенно не составляет расписание... просто инициирует планирование. Вы правы, TaskFactory не планирует... просто когда он создает задачу, он также инициирует процесс планирования. Совершенно отдельные вещи.

Ivan Petrov 21.06.2024 09:08

@IvanPetrov Я не понимаю. В 2024-06-21 05:58:29Z вы пригласили Панайотиса проверить ваш ответ, а в 2024-06-21 06:35:47Z вы пожаловались, что он прокомментировал ваш ответ. Что дает? Если вы не хотите спама, не приглашайте его. Кстати, комментарии под вашим ответом не являются спамом.

Theodor Zoulias 21.06.2024 09:56

«перечисление TaskCreationOptions официально является подмножеством TaskContinuationOptions для этого комментария в источнике» — возможно, вы захотите пересмотреть слово «официальный» в своем ответе. Я не думаю, что комментарии в исходном коде среды выполнения .NET являются официальными заявлениями Microsoft.

Theodor Zoulias 21.06.2024 10:01

@TheodorZoulias Я пригласил его сравнить его комментарии с тем, что я узнал, в основном в образовательных целях, и чтобы он прокомментировал ответ, как это сделали вы.

Ivan Petrov 21.06.2024 10:19

@TheodorZoulias отредактирую официальную часть, спасибо за предложение

Ivan Petrov 21.06.2024 10:21

Другие вопросы по теме