Параллелизм против многопоточности против асинхронного программирования: разъяснение

Хочу представить вашему вниманию перевод статьи Concurrency vs Multi-threading vs Asynchronous Programming: Explained.

В последние время, я выступал на мероприятиях и отвечал на вопрос аудитории между моими выступлениями о Асинхронном программировании, я обнаружил что некоторые люди путали многопоточное и асинхронное программирование, а некоторые говорили, что это одно и тоже. Итак, я решил разъяснить эти термины и добавить еще одно понятие Параллелизм. Здесь есть две концепции и обе они совершенно разные, первая синхронное и асинхронное программирование и вторая – однопоточные и многопоточные приложения. Каждая программная модель (синхронная или асинхронная) может работать в однопоточной и многопоточной среде. Давайте обсудим их подробно.

Синхронная программная модель – это программная модель, когда потоку назначается одна задача и начинается выполнение. Когда завершено выполнение задачи тогда появляется возможность заняться другой задачей. В этой модели невозможно останавливать выполнение задачи чтобы в промежутке выполнить другую задачу. Давайте обсудим как эта модель работает в одно и многопоточном сценарии.

Однопоточность – если мы имеем несколько задач, которые надлежит выполнить, и текущая система предоставляет один поток, который может работать со всеми задачами, то он берет поочередно одну за другой и процесс выглядит так:

image

Здесь мы видим, что мы имеем поток (Поток 1) и 4 задачи, которые необходимо выполнить. Поток начинает выполнять поочередно одну за одной и выполняет их все. (Порядок, в котором задачи выполняются не влияет на общее выполнение, у нас может быть другой алгоритм, который может определять приоритеты задач.

Многопоточность – в этом сценарии, мы использовали много потоков, которые могут брать задачи и приступать к работе с ними. У нас есть пулы потоков (новые потоки также создаются, основываясь на потребности и доступности ресурсов) и множество задач. Итак, поток может работать вот так:

image

Здесь мы можем видеть, что у нас есть 4 потока и столько же задач для выполнения, и каждый поток начинает работать с ними. Это идеальный сценарий, но в обычных условиях мы используем большее количество задач чем количество доступных потоков, таким образом освободившийся поток получает другое задание. Как уже говорилось создание нового потока не происходит каждый раз потому что для этого требуются системные ресурсы такие как процессор, память и начальное количество потоков должно быть определенным.

Теперь давайте поговорим о Асинхронной модели и как она ведет себя в одно и многопоточной среде.

Асинхронная модель программирования – в отличии от синхронной программной модели, здесь поток однажды начав выполнение задачи может приостановить выполнение сохранив текущее состояние и между тем начать выполнение другой задачи.

image

Здесь мы можем видеть, что один поток отвечает за выполнение всех задач и задачи чередуются друг за другом.

Если наша система способно иметь много потоков тогда все потоки могут работать в асинхронной модели как показано ниже:

image

Здесь мы можем видеть, что одна и та же задача скажем Т4, Т5, Т6 … обрабатывается несколькими потоками. Это красота этого сценария. Как мы можем видеть, что задача Т4 начала выполнение первой Потоком 1 и завершен Потоком 2. Подобным образом задча Т6 выполнена Потоком 2, Потоком 3 и Потоком 4. Это демонстрирует максимальное использование потоков.

Итак, до сих пор мы обсудили 4 сценария:

  • Синхронный однопоточный
  • Синхронный многопоточный
  • Асинхронный однопоточный
  • Асинхронный многопоточный

Давайте обсудим еще один термин – параллелизм.

Параллелизм

Проще говоря параллелизм способ обработки множественных запросом одновременно. Так как мы обсуждали два сценария когда обрабатывались множественные запросы, многопоточное программирование и асинхронная модель (одно и многопоточная). В случае асинхронной модели будь она однопоточной или многопоточной, в то время, когда выполняются множество задач, некоторые из них приостанавливаются, а некоторые выполняются. Существует много особенностей, но это выходит за рамки этой публикации.

Как обсуждалось ранее новая эпоха за асинхронным программированием. Почему это так важно?

Преимущества асинхронного программирования

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

Производительность приложения и системы также очень важны. Было замечено в то время как выполняется запрос, около 70-80% из них попадают в ожидании зависимых задач. Таким образом, это может быть максимально использовано в асинхронном программирование, где, как только задача передается другому потоку (например, SQL), текущий поток сохраняет состояние и доступен для выполнения другого процесса, а когда задача sql завершается, любой поток, который является свободным, может заняться этой задачей.

Асинхронность в ASP.NET

Асинхронность в ASP.NET может стать большим стимулом для повышения производительности вашего приложения. Вот, как IIS обрабатывает запрос:

image

Когда запрос получен IIS, он берет поток из пула потоков CLR (IIS не имеет какого-либо пула потоков, а сам вместо этого использует пул потоков CLR) и назначает его ему, который далее обрабатывает запрос. Поскольку количество потоков ограничено, и новые могут быть созданы с определенным пределом, тогда если поток будет находится большую часть времени в состоянии ожидания, то это сильно ударит по вашему серверу, вы можете предположить, что это реальность. Но если вы пишете асинхронный код (который теперь становится очень простым и может быть написан почти аналогично синхронному при использовании новых ключевых слов async / await), то он будет работать намного быстрее, и пропускная способность вашего сервера значительно возрастет, потому что вместо ожидания какого-нибудь завершения, он будет доступен пулу потоков, для нового запроса. Если приложение имеет множество зависимостей и длительный процесс выполнения, то для этого приложения асинхронное программирование будет не меньшем благом.

Итак, теперь мы поняли разницу многопоточного, асинхронного программирования и преимущества, которые мы можем получить, используя асинхронную модель программирования.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 54
  • 0
    Использование async/await действительно увеличит пропускную способность сервера, но у асинхронной машины есть накладные расходы которые немного увеличат время выполнения операции. Проще говоря если у вас не несколько сотен запросов в секунду, быстрее будет работать синхронный код.
    • +1

      … а будет ли это критично, если у вас не несколько сотен запросов в секунду?

      • +1
        Синхронный код начнет проигрывать когда начнет истощаться пул потоков. Оценив будет ли это происходить в вашей среде исполнения можно выбирать ту или иную модель кода. Кроме того синхронный код немного чище.
        • –2

          Можно сделать рассинхронизацию так, что вы не поймете когда начнёт "наполняться пул потоков". Собственно правильные диспетчеры тасков так и работают перераспределяя нагрузку по ядрам.

          • +2

            … а когда все ядра заняты?

            • –3

              Дебилам не отвечаю. Для остальных — если все ядра занят делом, то значит цель балансировки достигнута.

              • +1
                Для остальных — если все ядра занят делом, то значит цель балансировки достигнута.

                Не в данном случае. В данном случае потоки заняты не "делом", а ожиданием — хотя могли бы заниматься делом.

                • –2

                  Поток это канал, контейнерного типа где размещаются процессы или часть кода. Как любой канал он может простаивать, освобождаться. Это всё азы из теории СМО.Там до кучи терминов. Их надо знать, если хотите быть настоящим архитектором, а не фейковым. А так можете утверждать всё что угодно и на основании своих догм строить всё что угодно. К реальному делу это не имеет никакого отношения.

                  • +1
                    Поток это канал, контейнерного типа где размещаются процессы или часть кода.

                    Во-первых, нет.


                    Как любой канал он может простаивать, освобождаться.

                    Во-вторых, вот как раз для того, чтобы он освобождался, а не простаивал, и применяется асинхронное программирование.

                    • –1

                      Во-первых, да. Это не только контейнер, но ещё и агрегатор.
                      Во вторых, асинхронность призвана равномерно загружать тики, а не освобождать.
                      Есть еще в третьих и в четвертых. Но об этом как нибудь в следующий раз. Появление нового потока это как правило вынужденная мера, кода нельзя сдвинуть процесс по времени, не затрагивая смежных процессов Особенно в циклических алгоритмах.

                      • +1
                        Во-первых, да. Это не только контейнер, но ещё и агрегатор.

                        Дайте, пожалуйста, ссылку на мейнстримное определение, подтверждающее вашу точку зрения.


                        Во вторых, асинхронность призвана равномерно загружать тики, а не освобождать.

                        Поясните?


                        Появление нового потока это как правило вынужденная мера, кода нельзя сдвинуть процесс по времени, не затрагивая смежных процессов

                        Так одновременное обслуживание клиентов веб-сервером (или прикладным сервером) — это оно и есть.

                        • 0

                          Не могли бы вы пояснить на примере про циклические алгоритмы?


                          Как, к примеру, будет выглядеть одновременная передача трех файлов в три сокета на одном потоке?

                          • 0

                            Анимация например, обработка данных с неизменяемым контекстом. Про три сокета. Не знаю как будет выглядеть. Зависит от языка.

                            • 0

                              У-у-у, я понял в чем проблема.


                              Анимация — это особый случай. Во-первых, анимации обычно делаются путем периодических обновлений состояния по довольно простому закону, лишние абстракции тут только мешают (и, что еще хуже, уменьшают fps). Во-вторых, элементарные операции в анимациях — это зачастую CPU-bound операции.


                              Тем не менее, программа не может состоять только из анимаций. Попробуйте решить следующую задачу (можно и в уме): нужно загрузить данные, в процессе загрузки пользователю будет показываться анимированная заглушка. Как будет решаться такая задача? Источник данных — на ваш выбор, это может быть файл, удаленный сервер или что-то еще. Главное условие — анимация в заглушке никогда не должна замирать или двигаться "рывками".

            • 0
              Оценив будет ли это происходить в вашей среде исполнения можно выбирать ту или иную модель кода.

              А потом, в случае роста, радостно переписывать
              Чище за счет отсутствия слов async/await?
              • 0
                Это сугубо ваше дело как писать изначально. Чище за счет отсутствия слов async/await, необходимости оборачивать каждый результат в Task<> (ключевое слово var лучше не использую где возвращаемый тип не очевиден). Также пропадает необходимость писать ConfigureAwait(false) для каждой асинхронной задачи. Пропадает необходимость дописывать суффикс Async к названию метода как это требует MSDN
                • 0

                  ConfigureAwait(false) для каждой задачи писать и так не обязательно.

                  • 0

                    Обязательно если вы пишете библиотеку

                    • 0

                      Если вы про взаимоблокировки — то есть более красивые способы их избежать. Если про что-то другое — то жду аргументов.

                      • 0

                        Какие, кстати? Я бы с удовольствием почитал.

                        • 0
                          1. Task.Run(...)
                          2. что-то вроде await ContextSwitcher.SwitchToBackground(); (нестандартное решение, но широко распространенное)
                          3. просто не вызывать нигде Task.Wait или Task<>.Result

                          Первые два решения подходят для библиотек. Необходимость третьего можно прописать в документации на библиотеку.

                          • 0

                            Спасибо.


                            (третье мне точно не подходит, я людям не верю)

                      • +1

                        Нет, обязательно тогда, когда вы знаете, что кусок после await можно не выполнять в том же синхронизационном контексте. Это не зависит от "библиотека-не библиотека".


                        Да и то, на самом деле, не "обязательно", а желательно.

                        • 0
                          Если вы пишете библиотеку не только для себя то вы не можете знать где она будет работать. Если контекст синхронизации вам не нужен то необходимо его отпустить
                          • +1

                            Нет никакого "необходимо". Это правило хорошего тона по отношению к людям, которые будут использовать вашу библиотеку неправильно.


                            Особенно смешно это "необходимо" выглядит в ситуации, когда после await вызывается код, который вы не контролируете (например, переданный делегат или вброшенный сервис).

                            • 0
                              Эту рекомендацию я взял из Best Practice и всегда ей придерживаюсь. Если вы вызываете код который вы не контролируете то очевидно контекст синхронизации вам нужен, просто восстановите его перед вызовом делегата. Но до тех пор пока контекст вам не нужен, зачем его дергать после каждого await?

                              Вот пример с делегатом:
                              public async Task DoWorkAsync(Action<byte[]> callback)
                              {
                                  byte[] content = await GetFileAsync();
                                  Thread.Sleep(3000); // подготовка данных для делегата
                                  callback(content);
                              }


                              Вот как я бы это сделал:
                              public async Task DoWorkAsync(Action<byte[]> callback)
                              {
                                  var context = SynchronizationContext.Current;
                                  if(context != null)
                                      await new NoContextYieldAwaitable();
                              
                                  byte[] content = await GetFileAsync();
                                  Thread.Sleep(3000); // подготовка данных для делегата
                              
                                  if (context != null)
                                      await context.RestoreAsync();
                              
                                  callback(content);
                              }
                              
                              • 0

                                Не вижу в вашем коде ConfigureAwait(false). Видимо, писать его и не настолько обязательно :-)

                                • 0
                                  Вы зря придираетесь.
                                  public async Task DoWorkAsync(Action<byte[]> callback)
                                  {
                                      var context = SynchronizationContext.Current;
                                      byte[] content = await GetFileAsync().ConfigureAwait(false);
                                      Thread.Sleep(3000); // подготовка данных для делегата
                                  
                                      if (context != SynchronizationContext.Current)
                                          await context.RestoreAsync();
                                  
                                      callback(content);
                                  }
                                  • +1

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

                                    • 0
                                      Последнее я говорил
                                      Если контекст синхронизации вам не нужен то необходимо его отпустить

                                      Не столь важно как вы это сделаете, через ConfigureAwait или другим способом. Я лишь хочу показать на сколько это важно. К примеру возьму я библиотеку из nuget для WinForm и пока выполняется асинхронная функция она не должна прерывать UI без необходимости. Или еще хуже: может произойти дедлок если UI синхронно ждет завершение этого таска.
                                      • +1
                                        Я лишь хочу показать на сколько это важно.

                                        Ну пока в качестве доказательства важности вы только ссылаетесь на Best Practices.


                                        К примеру возьму я библиотеку из nuget для WinForm и пока выполняется асинхронная функция она не должна прерывать UI без необходимости.

                                        Вообще-то, первое, что нужно сделать при вызове асинхронной функции из UI — это вызывать ее на контексте, отличном от UI-ного. Не надо надеяться, что функция внутри сделает это сама.

                                      • –1
                                        при вызове асинхронной функции из UI — это вызывать ее на контексте, отличном от UI-ного.

                                        То-есть всегда писать так?
                                        await Task.Run(() => httpClient.GetAsync(...));
                                        • –1

                                          Где я писал про "всегда"?

                                          • 0

                                            Если этот код внутри UI-обработчика, и вы хотите полностью отзывчивый UI, то да. Но зачем у вас обращение к HTTP-клиенту внутри UI-обработчика?

                                    • 0

                                      Кстати, я бы этот код написал чуть проще:


                                      public async Task DoWorkAsync(Action<byte[]> callback)
                                      {
                                          byte[] content = null;
                                          await Task.Run(async () => 
                                          {
                                              content  = await GetFileAsync();
                                              Thread.Sleep(3000); // подготовка данных для делегата
                                          });
                                          callback(content);
                                      }
                                      • +1
                                        Эту рекомендацию я взял из Best Practice и всегда ей придерживаюсь.

                                        Это все же рекомендация, а не обязательство.


                                        Вот как я бы это сделал

                                        … вот вы и усложнили код безо всякой на то необходимости.

                            • +1
                              Для этого есть Fody и [ConfigureAwait(false)][assembly: Fody.ConfigureAwait(false)]
                          • 0
                            Синхронный код начнет проигрывать когда начнет истощаться пул потоков.

                            Вопрос не в том, когда один будет проигрывать другому, а в том, когда потери будут заметны.


                            Кроме того синхронный код немного чище.

                            Это зависит от того, какой интерфейс у следующего слоя. Если уже асинхронный, то нет, не чище.

                            • 0
                              Недавно искал причину, как раз порождающуюю такую проблему.
                              Так что
                              Вопрос не в том, когда один будет проигрывать другому, а в том, когда потери будут заметны.
                              очень корректное высказывание.
                              По нашим тестам получилось что «цена» асинхронности может составлять до 30%, или давать выйгрыш в 10% относительно «эталонного» синхронного исполнения. -10% +30% времени.
                              Основная проблема — соотношение создания «новых» задач со временем ожидания результата от уже запущенных. В худшем сценарии просто выжирался пул потоков, а после плавно возвращались результаты, но долго :) Наиболее оптимальный сценарий оказался, если скорость создания задач была +- равна скорости получения результатов, тогда количество потоков равномерно.
                              Что в принципе и так написано в любой книжке: выравнивайте нагрузку
                              • 0
                                По нашим тестам получилось что «цена» асинхронности может составлять до 30%,

                                Я не очень понимаю, каким образом асинхронная машина может давать накладные расходы, пропорциональные времени запроса (а не количеству точек асинхронии).

                                • 0
                                  А вот это другой очень и очень интересный вопрос. Есть пару подозрений, как освобожусь — проверю.
                                  Там уже выходит за границу нашего приложения. а где-то «на границе» происходит что-то любопытное
                      • 0
                        Отличие асинхронности от многопоточности хорошо проилюстрюрованно. А вот абзац про параллелизм написан сумбурно и с ошибками.
                        • 0
                          Таки конкурентность != параллелизм, очень часто путаются эти термины.
                          [1, 2, 3, 4, 5]
                          • +1
                            И снова статья, где автор преувеличивает недостатки синхронной модели и скрывает недостатки асинхронной.

                            Во-первых, графики, они построены так, чтобы казалось: «Воу! Асинхронная модель выполняет больше задач!», а по факту, там просто какие-то кубики без привязки ко времени и размерам задачи. Классический маркетинговый прием.

                            Во-вторых, возьмем к примеру цитату:
                            Здесь мы можем видеть, что одна и та же задача скажем Т4, Т5, Т6 … обрабатывается несколькими потоками. Это красота этого сценария. Как мы можем видеть, что задача Т4 начала выполнение первой Потоком 1 и завершен Потоком 2. Подобным образом задча Т6 выполнена Потоком 2, Потоком 3 и Потоком 4. Это демонстрирует максимальное использование потоков.

                            Автор рассказывает, как же это круто, что 4 потока обрабатывают один запрос, но ни слова не говорит про синхронизацию этих 4х потоков, чтобы они могли работать с shared state задачи.
                            т.е. на графике, между каждым кубиком нужно добавить еще добавить прослойку из задержек на синхронизацию состояний.
                            Так же автор ничего не сказал про то, что состояния должны быть «сохранены» в неком объекте для continuation (продолжения выполнения через время).

                            Доля правды в статье есть, асинхронность – это хороший подход, но не на столько хороший, как его расхваливает автор.
                            • 0
                              Распишу пример в цифрах:
                              Задача занимает 1000 ед. времени.
                              Задача может быть разделена на 4 подзадачи по 250 ед. времени.
                              Синхронизация «железных потоков» 10 ед. времени.
                              Сохранение состояния континуации 1 ед. времени (допустим она сверхлегкая).
                              Есть 4 потока и 8 задач.

                              вариант1. Каждая подзадача зависит от предыдущей и не может быть выполнена в параллели.
                              (подготовить запрос в базу, сходить в базу, обработать результат, отдать наружу)
                              вариант2. Каждая подзадача независима и может быть выполнена в параллели.
                              (проксировать запрос на 4 других сервиса, и больше ничего не делать)
                              вариани3. Каждая 2 подзадачи независимы, 2 – зависимы.
                              (подготовить 2 запроса, отправить 2 запроса в 2 разных системы, смержить ответы и отдать наружу)

                              «Синхронная» многопоточная модель:
                              ход выполнения: 2 задачи + 2 синхронизации
                              8 задач = 1000 + 10 + 1000 + 10 = 2020 ед. времени (в каждом потоке).
                              1 задача будет выполнена за 1010 ед. времени.

                              «Асинхронная» многопоточная модель:
                              #1: Каждая подзадача зависит от предыдущей и не может быть выполнена в параллели.
                              ход выполнения: 8 подзадач + 8 сохранений состояния + 8 синхронизаций.
                              8 задач = (8 * 250) + (8 * 1) + (8 * 10) = 2088 е.д. времени.
                              1 задача будет выполнена за: (4 * 250) + (4 * 1) + (4 * 10) = 1044 ед. времени

                              #2: Каждая подзадача независима и может быть выполнена в параллели.
                              ход выполнения: 8 подзадач + 8 сохранений состояния + 8 синхронизаций.
                              8 задач = (8 * 250) + (8 * 1) + (8 * 10) = 2088 е.д. времени.
                              1 задача будет выполнена за: (1 * 250) + (1 * 1) + (1 * 10) = 261 ед. времени

                              #3: Каждая 2 подзадачи независимы, 2 – зависимы.
                              ход выполнения: 8 подзадач + 8 сохранений состояния + 8 синхронизаций.
                              8 задач = (8 * 250) + (8 * 1) + (8 * 10) = 2088 е.д. времени.
                              1 задача будет выполнена за: (3 * 250) + (3 * 1) + (3 * 10) = 788 ед. времени
                              *но тут все на много интереснее: этот вариант не может быть идеально положен на таймлайн потоков, потому 3-4 задачи из 8 будет раскиданы по таймлайну и выполнены в среднем за:
                              (5 * 250) + (5 * 1) + (5 * 10) = 1305 ед. времени, на ~25% дольше чем в синхронной модели.

                              Подводя итог:
                              — Синхронная модель = стабильность и предсказуемость по времени обработки и отдачи результата.
                              — Асинхронная модель = менее предсказуема в поведении и необходимо изучать среду в которой будет использована данная модель и требования к системе.

                              В идеальном варианте (#2) такой подход может дать в ~3.5 раза больше скорость обработки. Но таких задач на практике крайне мало, если есть вообще.
                              В более реальном случае (#1), такой подход может даже стабильно замедлить систему.
                              Или сделать ее нестабильной (#3) – когда все зависит от планировщика подзадач и часть задач может отдаваться на 25% быстрее, а часть на 25% медленнее, чем в синхронной модели.

                              ps: в примере #3, если нагрузка будет не сильно плотной, то система получит стабильный прирост на ~25%, если же нагрузка на систему возрастет, то система станет менее стабильной и может перейти даже в разряд деградации производительности. Потому, в этом случае, нужно делать замеры и подстраиваться под возрастающую нагрузку заблаговременно.

                              pps: данные примеры – это крайне упрощенная теория, а не реальные исследования. В реальном мире все на много сложнее и сильно может отличаться от приведенных цифр!
                              • 0

                                Вот поэтому и говорят, что асинхрония в первую очередь выгодна для IO-bound задач, а не вычислительных.

                                • 0
                                  Да именно, и я прекрасно это понимаю.
                                  Но к сожалению, очень многие этого не понимают. А подобные статьи лишь усиливают мнение, что Асинхронность – это идеальный подход без недостатков.

                                  В статье явно прослеживается посыл: «синхронность – плохо, асинхронность – хорошо. точка.»
                            • –1
                              Важно помнить, что .net пытается быть умнее чем ему следовало бы. По умолчанию, если я не ошибаюсь, количество одновременных потоков на пуле равно количеству ядер процессора. И вот ваша супер-пупер параллельная программа на дефолтных настройках может вести себя совсем по разному на разных процессорах. Да, это всё решаемо через конфиги, но зачем усложнять? Если физических ядер нехватает, не значит что не нужно создавать новые потоки когда это напрямую говорит разработчик.
                              Похожая история с параллельными соединениями того же HttpClient или сервисов. Например вы хотите асинхронно в 10 запросов выкачать что-то с какого-то сайта. Всё будет работать, но сильно медленее чем вам хотелось бы. И вот вы убиваете не один час дебажа свой проект и проклиная криворуких разработчиков того сайта, который медленно качается. А может даже махаете рукой и пускаете в production. Но потом оказывается, что на сетевые запросы тоже пул и вы по умолчанию зачем-то ограничены 2-мя соединениями на сайт. Это тоже решаемо, но кто их просил? Спасибо разработчикам дотнета за столь очевидную функциональность!
                              Обычно если с инструментами приходится бороться, то это записывают в минус инструментам.
                              • +1
                                Если физических ядер нехватает, не значит что не нужно создавать новые потоки когда это напрямую говорит разработчик.

                                Когда программисту нужен новый поток — он его создает через new Thread. И никто не ограничивает число потоков созданных этим способом.


                                Если же разработчик использует другие инструменты — то он не "непрямую говорит", а завязывается на детали реализации. Ну и кто ему злобный буратина при этом?

                                • –2
                                  «Другие инструменты» продвигаются компанией разработчиком фреймворка как «best practices». Действительно, зачем им следовать. Особенно новым разработчикам.
                                  Да и с new Thread немного повезло, в те далёкие времена видно вещества не так торкали разработчиков .net. А вот создание «напрямую» new HttpClient абсолютно ничем не поможет. Всё равно будет ограничение на количество параллельных сетевых соединений. «Детали реализации» спрятаны где-то глубоко в виде анального зонда. Кому такой заднеприводный подход доставляет удовольствие, могут быть радостными буратинами. Каждому своё.
                                  • +1
                                    «Другие инструменты» продвигаются компанией разработчиком фреймворка как «best practices».

                                    Ну так в тех же best practices и объясняется, как эти инструменты работают.

                                • +1
                                  на сетевые запросы тоже пул и вы по умолчанию зачем-то ограничены 2-мя соединениями на сайт.

                                  Ну как "зачем-то"...


                                  A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy.

                                  HTTP/1.1

                                  • +1
                                    RFC2616 obsolete уже далеко не один год. И это условное ограничение, которое и так носило больше рекомендательный характер, сто лет в обед как убрали. От сетевого примитива подобное поведение не ожидается вообще, так что кроме как подводным камнем подобное не назовёшь. Тут просто нечего защищать и оправдывать.
                                    • +1

                                      Ожидания — вещь хрупкая и неожиданная, люди таких разных вещей ожидают или не ожидают… вот то, что в документации на видном месте этого нет — это правда печаль.


                                      (В той же документации, кстати, неоднократно сказано "не надо создавать новые HttpClient, неполезно это".)

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.