Здравствуйте, StatujaLeha, Вы писали:
SL>2. Ставим на паузу таску ContinueWith.
Это как?
SL>4. Идем обратно в таску ContinueWith и завершаем ее.
Откуда идем-то?
SL>Т.е. мы тратим на таску ContinueWith ресурсы только тогда, когда оно реально необходимо.
В рассматриваем примере этот таск завершится сразу после вызова WhenAll, то есть ресурсов на нее не будет затрачено совсем. Ресурсы будут использованы в таске, который создаст метод WhenAll.
SL>В примере через await оно так и должно работать.
Это потому что контекст захватили, а теперь добавь .ConfigureAwait(false) и посмотри что будет с таской ContinueWith.
SL>Пришли мы к вызову Parallel.Invoke, запустили все таски after.
Parallel.Invoke сам создает и запускает таски. Ему передаются экшены.
SL>И как тредпул теперь поймет, что до завершения всех тасков after не надо тратить вычислительные ресурсы на таску, из которой запущен Parallel.Invoke?
Почему не надо то, если там работает Parallel.Invoke.
Здравствуйте, Nikolay_Ch, Вы писали:
N_C>Здравствуйте, StatujaLeha, Вы писали:
SL>>И как тредпул теперь поймет, что до завершения всех тасков after не надо тратить вычислительные ресурсы на таску, из которой запущен Parallel.Invoke? N_C>Посмотрите в исходники.Net... Там есть несколько вариантов работы Invoke, в зависимости от количества переданных Action'ов... Один из которых WaitAll, другой — FastWaitAll...
Спасибо.
Т.е. там во всех ветках какой-либо Wait на другие таски.
Ждем на ManualResetEventSlim.
Тут мне и непонятно: если у меня есть Task и внутри него вызывается какой-то Task.Wait*, то как будет работать тредпул с этой таской?
Допустим, у нас первая таска завершается быстро, а остальные работают большее время.
В случае, ContinueWith(async (_) => await Task.WhenAll(...)) все равно будет один вход в таску с await и один выход из нее.
Я подозреваю, что в случае ContinueWith(() => Parallel.Invoke(...)) тред пул будет периодически давать время таске с Invoke и она будет просто тратить время на ожидание завершения остальных тасков, не выполняя полезной работы.
SL>В случае, ContinueWith(async (_) => await Task.WhenAll(...)) все равно будет один вход в таску с await и один выход из нее. SL>Я подозреваю, что в случае ContinueWith(() => Parallel.Invoke(...)) тред пул будет периодически давать время таске с Invoke и она будет просто тратить время на ожидание завершения остальных тасков, не выполняя полезной работы.
Ну так посмотрите в исходниках в чем отличие WhenAll от WaitAll и FastWaitAll
Думаю, что Вы удивитесь (а может и нет — смотря с какой стороны посмотреть)
Здравствуйте, _Raz_, Вы писали:
SL>>2. Ставим на паузу таску ContinueWith. _R_>Это как?
Через await. Тред пул не будет передавать таску потоку из пула, пока не завершится таска под await-ом.
SL>>4. Идем обратно в таску ContinueWith и завершаем ее. _R_>Откуда идем-то?
Тредпул мониторит, что завершилась таска под await-ом и это сигнал, что можно продолжить таску, которая содержит этот await.
SL>>Т.е. мы тратим на таску ContinueWith ресурсы только тогда, когда оно реально необходимо. _R_>В рассматриваем примере этот таск завершится сразу после вызова WhenAll, то есть ресурсов на нее не будет затрачено совсем. Ресурсы будут использованы в таске, который создаст метод WhenAll.
Сценарий: первая таска из after выполняется очень быстро, оставшиеся медленно.
Получится, что Parallel.Invoke будет тратить ресурсы на то, чтобы периодически чекать, завершились ли задачи из after.
А этого можно избежать.
SL>>И как тредпул теперь поймет, что до завершения всех тасков after не надо тратить вычислительные ресурсы на таску, из которой запущен Parallel.Invoke? _R_>Почему не надо то, если там работает Parallel.Invoke.
А смысл?
Туда нужно вернуться один раз, когда все задачи завершены.
В примере через await так и произойдет.
Здравствуйте, StatujaLeha, Вы писали:
SL>Сценарий: первая таска из after выполняется очень быстро, оставшиеся медленно. SL>Получится, что Parallel.Invoke будет тратить ресурсы на то, чтобы периодически чекать, завершились ли задачи из after.
Это откуда такое получается? В Parallel.Invoke нет никакого периодического чеканья.
SL>А смысл?
Не забывай, что в Parallel.Invoke передаются экшены и фабрика тасков для этих экшенов должна где-то выполняться.
SL>Туда нужно вернуться один раз, когда все задачи завершены.
А Parallel.Invoke никуда и не уходил, а это менее затратно по ресурсам
SL>В примере через await так и произойдет.
А дальше что? А дальше сразу конец метода. Вот и получилось, что держали-держали поток и сразу вышли Зачем держали
Здравствуйте, _Raz_, Вы писали:
_R_>Это откуда такое получается? В Parallel.Invoke нет никакого периодического чеканья.
Есть. Если чекнуть вторую ссылку на исходники фреймворка, которую я дал в сообщении ранее, то там будет функция WaitAllBlockingCore.
Parallel.Invoke может по разному запустить переданные ему Action-ы, но в конце всегда будет какой-либо Wait.
Изучив Wait-ы, можно понять, что ожидание будет выполняться через ManualResetEventSlim. https://msdn.microsoft.com/en-us/library/system.threading.manualreseteventslim(v=vs.110).aspx
ManualResetEventSlim uses busy spinning for a short time while it waits for the event to become signaled.
_R_>Не забывай, что в Parallel.Invoke передаются экшены и фабрика тасков для этих экшенов должна где-то выполняться.
Так и в чем проблема?
Например, если экшенов относительно мало, то все выполняется следующим кодом из потока, запустившего Invoke
// Launch all actions as tasksfor (int i = 1; i < tasks.Length; i++)
{
tasks[i] = Task.Factory.StartNew(actionsCopy[i], parallelOptions.CancellationToken, TaskCreationOptions.None,
InternalTaskOptions.None, parallelOptions.EffectiveTaskScheduler);
}
_R_>А Parallel.Invoke никуда и не уходил, а это менее затратно по ресурсам
Как бы в этом и нюанс, что в некоторых сценариях таска с Parallel.Invoke будет кушать вычислительные ресурсы, не направленные на выполнение задач after.
Так что это точно не менее затратно по ресурсам.
_R_>А дальше что? А дальше сразу конец метода. Вот и получилось, что держали-держали поток и сразу вышли Зачем держали
Потоками тредпул рулит. А не таски.
И выделенное сообщение: суть await-а в том, что при его достижении тредпула отберет поток у задачи и не будет назначать до тех пор, пока таска под await-ом не завершится.
Здравствуйте, StatujaLeha, Вы писали:
SL>Как бы в этом и нюанс, что в некоторых сценариях таска с Parallel.Invoke будет кушать вычислительные ресурсы, не направленные на выполнение задач after.
Погодите-ка... Но WhenAll работает так же, как и Parallell.Invoke. Где будет экономия?
Здравствуйте, Nikolay_Ch, Вы писали:
N_C>Ну так посмотрите в исходниках в чем отличие WhenAll от WaitAll и FastWaitAll N_C>Думаю, что Вы удивитесь (а может и нет — смотря с какой стороны посмотреть)
Да, посмотрел.
WhenAll сам по себе не делает busy waiting, а WaitAll и FastWaitAll делают.
Выходит, что я убираю один busy waiting, если делаю через await.
Или я что-то не учел?
Здравствуйте, StatujaLeha, Вы писали:
SL>WhenAll сам по себе не делает busy waiting, а WaitAll и FastWaitAll делают.
Не делает, но (судя по исходникам) и параллельно задачи не запускает, т.е. выполняет их последовательно одну за одной...
Здравствуйте, StatujaLeha, Вы писали:
SL>Есть. Если чекнуть вторую ссылку на исходники фреймворка, которую я дал в сообщении ранее, то там будет функция WaitAllBlockingCore. SL>Parallel.Invoke может по разному запустить переданные ему Action-ы, но в конце всегда будет какой-либо Wait. SL>Изучив Wait-ы, можно понять, что ожидание будет выполняться через ManualResetEventSlim. SL>https://msdn.microsoft.com/en-us/library/system.threading.manualreseteventslim(v=vs.110).aspx SL>
SL>ManualResetEventSlim uses busy spinning for a short time while it waits for the event to become signaled.
Про чеканье стало понятно. Теперь возник вопрос в чем разница Parallel.Invoke и Task.WaitAll в разрезе этого чеканья.
_R_>>Не забывай, что в Parallel.Invoke передаются экшены и фабрика тасков для этих экшенов должна где-то выполняться. SL>Так и в чем проблема?
Так и нет проблемы. Это ответ на "А смысл? Туда нужно вернуться один раз, когда все задачи завершены.".
SL>Например, если экшенов относительно мало, то все выполняется следующим кодом из потока, запустившего Invoke
Если ты видишь и понимаешь этот код, то смысл чего тебе не понятен?
SL>Как бы в этом и нюанс, что в некоторых сценариях таска с Parallel.Invoke будет кушать вычислительные ресурсы, не направленные на выполнение задач after.
А что так загадочно? Рассказывай про сценарии и озвучь список ресурсов.
SL>Так что это точно не менее затратно по ресурсам.
Это "точно" учитывает все if-ы внутри Parallel.Invoke и учитывает отсутствие переключение контекстов и дороговизну создания новых потоков (это про MaxDegreeOfParallelism)?
_R_>>А дальше что? А дальше сразу конец метода. Вот и получилось, что держали-держали поток и сразу вышли Зачем держали SL>Потоками тредпул рулит. А не таски.
И что? Поток то все равно есть и он занимает ресурсы (не направленные на выполнение задач after ).
SL>И выделенное сообщение: суть await-а в том, что при его достижении тредпула отберет поток у задачи и не будет назначать до тех пор, пока таска под await-ом не завершится.
Суть await-а в синтаксическом сахаре. Если переписать на продолжениях суть изменится или нет?
И можно про то как тредпул отбирает поток у задачи?
Здравствуйте, Nikolay_Ch, Вы писали:
N_C>Не делает, но (судя по исходникам) и параллельно задачи не запускает, т.е. выполняет их последовательно одну за одной...
Он их вообще не запускает.
Поэтому в моем примере туда передаются задачи из Task.Run, т.е. уже запущенные(это обеспечит параллельность).
Здравствуйте, StatujaLeha, Вы писали:
N_C>>Не делает, но (судя по исходникам) и параллельно задачи не запускает, т.е. выполняет их последовательно одну за одной... SL>Он их вообще не запускает.
Согласен, недосмотрел.
SL>Поэтому в моем примере туда передаются задачи из Task.Run, т.е. уже запущенные(это обеспечит параллельность). SL>
Здравствуйте, _Raz_, Вы писали:
_R_>Про чеканье стало понятно. Теперь возник вопрос в чем разница Parallel.Invoke и Task.WaitAll в разрезе этого чеканья.
Ок.
Между Parallel.Invoke и Task.WaitAll нет разницы.
А между Parallel.Invoke и Task.When All она есть.
_R_>Так и нет проблемы. Это ответ на "А смысл? Туда нужно вернуться один раз, когда все задачи завершены.".
Так и я думаю, что проблемы нет. Просто я не понял, к чему замечание "Не забывай, что в Parallel.Invoke передаются экшены и фабрика тасков для этих экшенов должна где-то выполняться."?
Это же ни на что не влияет.
_R_>Если ты видишь и понимаешь этот код, то смысл чего тебе не понятен?
Мне как раз все понятно
_R_>А что так загадочно? Рассказывай про сценарии и озвучь список ресурсов.
_R_>Это "точно" учитывает все if-ы внутри Parallel.Invoke и учитывает отсутствие переключение контекстов и дороговизну создания новых потоков (это про MaxDegreeOfParallelism)?
Ты о чем? В примере топикстартера Parallel.Invoke точно так же скидывает таски в тред пул, в котором все потоки уже созданы.
Таски в обоих вариантах запускаются идентично.
_R_>И что? Поток то все равно есть и он занимает ресурсы (не направленные на выполнение задач after ).
Это не так. Видимо, надо изучить матчасть.
_R_>Суть await-а в синтаксическом сахаре. Если переписать на продолжениях суть изменится или нет? _R_>И можно про то как тредпул отбирает поток у задачи?
Ясно
Рекомендую изучить матчать: async/await вообще-то заставляют компилятор трансформировать функцию.
Google в помощь: "async await state machine".
Не то чтобы чтиво большое, но и не пару строк.
Здравствуйте, Nikolay_Ch, Вы писали:
N_C>Ну, тогда у меня последний аргумент — Parallel может лучше суметь распараллелить таски, чем последовательный их запуск...
В смысле?
При дефолтных аргументах(как у автора) что в моем примере, что внутри у Parallel.Invoke таски будут идентично запущены.
PS Task.Run() не дожидается окончания работы таски, которую он создал.
Здравствуйте, StatujaLeha, Вы писали:
SL>Ок. SL>Между Parallel.Invoke и Task.WaitAll нет разницы. SL>А между Parallel.Invoke и Task.When All она есть.
Ок.
SL>Так и я думаю, что проблемы нет. Просто я не понял, к чему замечание "Не забывай, что в Parallel.Invoke передаются экшены и фабрика тасков для этих экшенов должна где-то выполняться."? SL>Это же ни на что не влияет.
Это влияет на то, почему Parallel.Invoke работает в потоке ContinueWith — в этом же твое изначальное замечание
было. А теперь, оказывается, что ни на что не влияет
_R_>>А что так загадочно? Рассказывай про сценарии и озвучь список ресурсов. SL>Я его уже озвучивал тебе: https://rsdn.org/forum/dotnet/6904266.1
Это вот этот: "Сценарий: первая таска из after выполняется очень быстро, оставшиеся медленно."?
И из него ты делаешь вывод: "Как бы в этом и нюанс, что в некоторых сценариях таска с Parallel.Invoke будет кушать вычислительные ресурсы, не направленные на выполнение задач after."?
SL>Ты о чем?
Я о категоричности "это точно не менее затратно по ресурсам".
SL>В примере топикстартера Parallel.Invoke точно так же скидывает таски в тред пул, в котором все потоки уже созданы. SL>Таски в обоих вариантах запускаются идентично.
Значит ты смотрел исходники Parallel.Invoke, сказал, что понял их, и все равно настаиваешь на категоричном "идентично"?
_R_>>И что? Поток то все равно есть и он занимает ресурсы (не направленные на выполнение задач after ). SL>Это не так. Видимо, надо изучить матчасть.
Значит, все-таки настаиваешь, что "тредпул отберет поток"?
SL>Ясно SL>Рекомендую изучить матчать: async/await вообще-то заставляют компилятор трансформировать функцию. SL>Google в помощь: "async await state machine". SL>Не то чтобы чтиво большое, но и не пару строк.
Воспользовался твоими рекомендациями, но подтверждения для "суть await-а в том, что при его достижении тредпула отберет поток у задачи и не будет назначать до тех пор, пока таска под await-ом не завершится" не нашел. Так что позволю себе повторить вопросы:
Если await переписать на продолжениях суть изменится или нет?
И можно про то как тредпул отбирает поток у задачи?
Здравствуйте, StatujaLeha, Вы писали:
SL>Здравствуйте, Nikolay_Ch, Вы писали: SL>В смысле? SL>При дефолтных аргументах(как у автора) что в моем примере, что внутри у Parallel.Invoke таски будут идентично запущены.
Вот из документации:
// In the algorithm below, if the number of actions is greater than this, we automatically
// use Parallel.For() to handle the actions, rather than the Task-per-Action strategy.
В общем, все Экшны стартуют как Таски только для количества меньше 10...
Здравствуйте, Nikolay_Ch, Вы писали:
N_C>Вот из документации: N_C>
N_C>// In the algorithm below, if the number of actions is greater than this, we automatically
N_C>// use Parallel.For() to handle the actions, rather than the Task-per-Action strategy.
N_C>В общем, все Экшны стартуют как Таски только для количества меньше 10...
было. А теперь, оказывается, что ни на что не влияет
Чего?
Мое изначальное замечание действительно в этом сообщении. Как мне кажется, оно ясно сформулировано
Как я понял по описанию Parallel.Invoke, получится, что ContinueWith запускает Parallel.Invoke и ждет, пока он отработает(т.е. таска ContinueWith будет периодически получать время на выполнение, даже если не все задачи завершены).
И мой вариант убирает это ожидание.
_R_>>>А что так загадочно? Рассказывай про сценарии и озвучь список ресурсов. SL>>Я его уже озвучивал тебе: https://rsdn.org/forum/dotnet/6904266.1
_R_>Это вот этот: "Сценарий: первая таска из after выполняется очень быстро, оставшиеся медленно."? _R_>И из него ты делаешь вывод: "Как бы в этом и нюанс, что в некоторых сценариях таска с Parallel.Invoke будет кушать вычислительные ресурсы, не направленные на выполнение задач after."?
Да, я так думаю. Если первая таска выполнится быстро, то пойдет ожидание на ManualResetEventSlim, busy waiting.
SL>>Ты о чем? _R_>Я о категоричности "это точно не менее затратно по ресурсам".
Ну ок. У меня пока замеров нету, так что снимаю это утверждение.
Тогда попрошу как-то подкрепить утверждение отсюда: http://rsdn.org/forum/dotnet/6904317.1
А Parallel.Invoke никуда и не уходил, а это менее затратно по ресурсам
Как бы выходит, что в приведенном мной сценарии Parallel.Invoke действительно никуда не уходил и кушал ресурсы на ManualResetEventSlim.
Почему же это считается менее затратным?
_R_>Значит ты смотрел исходники Parallel.Invoke, сказал, что понял их, и все равно настаиваешь на категоричном "идентично"?
Согласен, слово "идентично" не подходит.
Если нет возражений, то заменю на "схожим образом".
Мой запуск
// Launch all actions as tasksfor (int i = 1; i < tasks.Length; i++)
{
tasks[i] = Task.Factory.StartNew(actionsCopy[i], parallelOptions.CancellationToken, TaskCreationOptions.None,
InternalTaskOptions.None, parallelOptions.EffectiveTaskScheduler);
}
// Optimization: Use current thread to run something before we block waiting for all tasks. tasks[0] = new Task(actionsCopy[0]);
tasks[0].RunSynchronously(parallelOptions.EffectiveTaskScheduler);
Разница в выделенном.
_R_>Значит, все-таки настаиваешь, что "тредпул отберет поток"?
https://www.codeproject.com/Articles/535635/Async-Await-and-the-Generated-StateMachine
Можешь сам убедиться: в состояниях State Machine, соответствующих await-ам, делается запуск операции под await-ом и дальше выход из функции MoveNext.
После этого поток свободен.
_R_>Воспользовался твоими рекомендациями, но подтверждения для "суть await-а в том, что при его достижении тредпула отберет поток у задачи и не будет назначать до тех пор, пока таска под await-ом не завершится" не нашел. Так что позволю себе повторить вопросы:
Ок
Как объяснишь результаты работы кода ниже?
Особенно выделенной части, когда исходный Action до await-а обрабатывается тредом 4, после await-а тредом 3, а Continue снова тредом 4.
_R_>Если await переписать на продолжениях суть изменится или нет?
Кинь плз пример кода, как ты предлагаешь это сделать.
_R_>И можно про то как тредпул отбирает поток у задачи?
Я механику в деталях не знаю, но выше привел пример, где зафиксировано такое поведение + ссылку на декомпилированную state machine, в которой тоже видно, что после await-а поток освобождается.
Того
SL>Мое изначальное замечание действительно в этом сообщении. Как мне кажется, оно ясно сформулировано SL>
SL>Как я понял по описанию Parallel.Invoke, получится, что ContinueWith запускает Parallel.Invoke и ждет, пока он отработает(т.е. таска ContinueWith будет периодически получать время на выполнение, даже если не все задачи завершены).
А я тебе на это отвечаю — "Не забывай, что в Parallel.Invoke передаются экшены и фабрика тасков для этих экшенов должна где-то выполняться."
То есть он не ждет, а выполняет полезную работу. Так понятней, надеюсь
SL>И мой вариант убирает это ожидание.
Я отвечал на то что до скобок.
SL>Да, я так думаю. Если первая таска выполнится быстро, то пойдет ожидание на ManualResetEventSlim, busy waiting.
Так оно в любом случае будет. Не обязательно busy, но будет.
SL>Как бы выходит, что в приведенном мной сценарии Parallel.Invoke действительно никуда не уходил и кушал ресурсы на ManualResetEventSlim. SL>Почему же это считается менее затратным?
И в выделенном, и до выделенного, и после выделенного. Я все пытаюсь тебя подтолкнуть, что Parallel.Invoke это чуть больше запуска потоков — там и некоторая оптимизация и сбор ошибок.
SL>Можешь сам убедиться: в состояниях State Machine, соответствующих await-ам, делается запуск операции под await-ом и дальше выход из функции MoveNext.
Я спрашивал про тредпул. Как называется его метод отбирающий поток?
SL>Как объяснишь результаты работы кода ниже? SL>Особенно выделенной части, когда исходный Action до await-а обрабатывается тредом 4, после await-а тредом 3, а Continue снова тредом 4.
Хватит уже щеки надувать. Ты меня не собеседуешь.
SL>Кинь плз пример кода, как ты предлагаешь это сделать.
Вот сейчас было ваще на отличненько
SL> выше привел пример, где зафиксировано такое поведение + ссылку на декомпилированную state machine, в которой тоже видно, что после await-а поток освобождается.
Значит даю направление: тредпул во всей этой истории не играет ни малейшей роли и ничего ни у кого не отбирает. Так же как и await — да, да, он все еще синтаксический сахар и не важно во что он там разворачивается (кстати, ты и тут ошибаешься: разворачивается async, а не await). А играют тут шедулер и контекст выполнения.