Я попытался создать экземпляр 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?





Это было не так очевидно, как я ожидал.
Первое, что важно знать, это то, что перечисление 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, которые используются одновременно для двух целей:
TaskCreationOptions будет извлечено и использовано при построении ContinuationTaskFromTaskContinueWithTaskContinuation, которая инкапсулирует их вместе с 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.
}
}
@PanagiotisKanavos «Он не планирует их выполнение», а через StartNew… еще раз спасибо за спам.
В документации ContinueWhenAll упоминаются некоторые недопустимые параметры, но я подозреваю, что никто не ожидал, что кто-то укажет параметр по умолчанию, фактически не используя значение по умолчанию, None. Эти методы предназначены для сложных сценариев и часто не документируются так подробно.
Это не спам. На самом деле я использовал все это для асинхронных конвейеров до 2012 года, до выхода Dataflow. Вот почему я знаю, где находится код и статьи. Я мог бы проголосовать за закрытие этого очевидно основанного на мнении вопроса или проголосовать против этого явно неправильного ответа, но я подозреваю, что это проблема XY, а реальная проблема X стоит того, чтобы ее решить.
Единственный способ ответить на ваш вопрос — посмотреть на код, вину Git, проблемы с Github и, возможно, задать вопрос самому Стивену Таубу. Люди задавали подобные вопросы десять лет назад, и он ответил в Почему TaskContinuationsOptions.ExecuteSynchronous согласен?: For a relatively advanced feature, I’ve been surprised how often this question has come up recently.
Тот факт, что этот конструктор не документирован полностью, ясно указывает на то, что он предназначен для сложных сценариев. В противном случае за последние 14 лет многие люди спрашивали бы об этом достаточно, чтобы упомянуть об этом в документах.
@PanagiotisKanavos, ты на самом деле не читал мой ответ и не смотрел источник, на который я ссылался. Главный вывод заключается в том, что TaskContinuationOptions является расширенным набором TaskCreationOptions, и это единственная причина, по которой RunContinuationsAsynchronoly является его частью (который, кстати, был представлен в .NET Framework 4.6) 2015 года или около того, возможно, вы застряли в 2012 году.
it does via StartNew. неправильно, и это тщательно задокументировано. Проверьте метод , вы увидите, что он в конечном итоге создает Task, который в конечном итоге ставит себя в очередь к планировщику
Наоборот, я не только смотрел на него со вчерашнего дня, но и следил за реальными звонками. Я должен сказать, что вы не просматривали эти ссылки, иначе вы бы увидели, что они из тех же файлов, которые вы просматривали.
@PanagiotisKanavos перечитал мой последний комментарий. Также посмотрите этот пост от 2015 года devblogs.microsoft.com/pfxteam/new-task-apis-in-net-4-6, документирующий, когда параметр был добавлен в перечисления.
Итак, вы не согласны с реальным исходным кодом Task.cs, который вызывает m_taskScheduler.InternalQueueTask(this); ?
@PanagiotisKanavos, никаких рассуждений, только утверждения? Что не так? Я до сих пор не видел соответствующего комментария, в котором обсуждались бы вещи, которые я написал, особенно тот факт, что одно является подмножеством другого и существует четкий код того, как оно преобразуется, и что оно служит двойной цели.
@PanagiotisKanavos какое отношение это имеет (то есть не к спаму) к моему ответу?
Вы утверждали it does via StartNew.. thanks for the spam again. Это просто неправильно (включая определение спама). Я все еще думаю, что у вас другая проблема, и предполагаю, что вы можете решить ее с помощью слишком сложного кода, а опытные разработчики помогут вам найти простое решение. Однако на данный момент, я думаю, вам следует выяснить это самостоятельно. Мне жаль, что я потратил свое время
@PanagiotisKanavos, ты не прокомментировал ни одну вещь, которую я написал в ответе, то есть спам. Затем вы заявили, что TaskFactory просто создает новые объекты, не планируя их, что, если бы вы прочитали ответ, не имеет значения, то есть спам. Но это также НЕПРАВИЛЬНО, как вы можете видеть source.dot.net/#System.Private.CoreLib/src/libraries/…: особенно по этому комментарию // Create and schedule the task. This throws an InvalidOperationException if already shut down.
Вы репостите мои ссылки. Теперь прочитайте 14 строк ниже, под номером 1155, и посмотрите, что делает метод.
@PanagiotisKanavos, ты имеешь в виду t.ScheduleAndStart(false);, который определенно не составляет расписание... просто инициирует планирование. Вы правы, TaskFactory не планирует... просто когда он создает задачу, он также инициирует процесс планирования. Совершенно отдельные вещи.
@IvanPetrov Я не понимаю. В 2024-06-21 05:58:29Z вы пригласили Панайотиса проверить ваш ответ, а в 2024-06-21 06:35:47Z вы пожаловались, что он прокомментировал ваш ответ. Что дает? Если вы не хотите спама, не приглашайте его. Кстати, комментарии под вашим ответом не являются спамом.
«перечисление TaskCreationOptions официально является подмножеством TaskContinuationOptions для этого комментария в источнике» — возможно, вы захотите пересмотреть слово «официальный» в своем ответе. Я не думаю, что комментарии в исходном коде среды выполнения .NET являются официальными заявлениями Microsoft.
@TheodorZoulias Я пригласил его сравнить его комментарии с тем, что я узнал, в основном в образовательных целях, и чтобы он прокомментировал ответ, как это сделали вы.
@TheodorZoulias отредактирую официальную часть, спасибо за предложение
Не совсем. TaskFactory создает только новые объекты задач либо как новые задачи, либо как продолжение нескольких задач. Он не планирует их выполнение. Вы можете указать любой флаг, но если планировщик однопоточный, все они будут выполняться в одном потоке. Если вы проверите код в TaskFactory.cs, вы увидите, что эти параметры продолжения используются только для
ContinueWhenAnyиContinueWhenAll.