В чем разница между асинхронным программированием и многопотоковой обработкой?

Я думал, что это в основном одно и то же - написание программ, которые разделяют задачи между процессорами (на машинах с процессорами 2+). Затем я читаю это, где написано:

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

Ключевые слова async и await не приводят к созданию дополнительных потоков создано. Асинхронные методы не требуют многопоточности, потому что асинхронные Метод не работает в своем собственном потоке. Метод работает на текущем контекст синхронизации и использует время в потоке, только когда метод активен. Вы можете использовать Task.Run для перемещения работы с процессором в фоновый поток, но фоновый поток не помогает с процессом это просто ожидание результатов, чтобы стать доступными.

и мне интересно, может ли кто-нибудь перевести это на английский для меня. Кажется, что проводится различие между асинхронностью (это слово?) И многопоточностью, и подразумевается, что у вас может быть программа, которая имеет асинхронные задачи, но не поддерживает многопоточность.

Теперь я понимаю идею асинхронных задач, таких как пример на pg. 467 из Jon Skeet С# In Depth, третье издание

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

Ключевое слово async означает "Эта функция, когда бы она ни вызывалась, не будет вызываться в контексте, в котором ее завершение требуется для всего после вызова".

Другими словами, писать это в середине какого-то задания

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

поскольку DisplayWebsiteLength() не имеет ничего общего с x или y, вызовет выполнение DisplayWebsiteLength() "в фоновом режиме", как

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

Очевидно, что это глупый пример, но я прав или я совершенно сбит с толку или как?

(Кроме того, меня смущает, почему sender и e никогда не используются в теле вышеуказанной функции.)

Ответ 1

Ваше непонимание чрезвычайно распространено. Многие люди учат, что многопоточность и асинхронность - одно и то же, но это не так.

Обычно помогает аналогия. Вы готовите в ресторане. Приказ приходит для яиц и тостов.

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

Теперь имеет ли смысл, что многопоточность - это только один вид асинхронности? Threading - о рабочих; асинхронность - это задачи. В многопоточных рабочих процессах вы назначаете задачи рабочим. В асинхронных однопоточных рабочих процессах у вас есть график задач, в которых некоторые задачи зависят от результатов других; по мере завершения каждой задачи он вызывает код, который планирует следующую задачу, которая может выполняться, с учетом результатов только что выполненной задачи. Но вам (надеюсь) нужен только один работник для выполнения всех задач, а не один рабочий на задание.

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

Итак, посмотрим на пример Джона более подробно. Что происходит?

  • Кто-то вызывает DisplayWebSiteLength. Кто? Нам все равно.
  • Он устанавливает метку, создает клиента и запрашивает у клиента что-нибудь. Клиент возвращает объект, представляющий задачу получения чего-либо. Эта задача выполняется.
  • Выполняется ли он в другом потоке? Возможно нет. Прочитайте статью Стивена о том, почему нет нити.
  • Теперь мы ждем задания. Что происходит? Мы проверяем, завершилась ли задание между тем временем, когда мы его создали, и мы ждали его. Если да, то мы получаем результат и продолжаем работать. Предположим, что он еще не закончен. Мы записываем оставшуюся часть этого метода в качестве продолжения этой задачи и возвращаем.
  • Теперь управление возвращается вызывающему. Что оно делает? Что бы он ни хотел.
  • Теперь предположим, что задача завершена. Как это произошло? Возможно, он работал в другом потоке или, возможно, вызывающем, который мы только что вернули, чтобы он мог работать до завершения в текущем потоке. Несмотря на это, у нас теперь есть завершенная задача.
  • Завершенная задача запрашивает правильный поток - опять же, скорее всего, единственный поток - для запуска продолжения задачи.
  • Контроль немедленно возвращается обратно в метод, который мы только что оставили в точке ожидания. Теперь доступен результат, поэтому мы можем назначить text и запустить оставшуюся часть метода.

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

Ответ 2

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

Вам не нужно беспокоиться о том, что несколько фрагментов кода касаются одних и тех же объектов одновременно: каждая функция завершит работу до того, как любой другой javascript будет запущен на странице.

Однако, когда вы делаете что-то вроде запроса AJAX, никакого кода не запускается вообще, поэтому другой javascript может реагировать на такие события, как события click, пока этот запрос не вернется и не вызовет связанный с ним обратный вызов. Если один из этих других обработчиков событий все еще работает, когда запрос AJAX возвращается, его обработчик не будет вызываться до тех пор, пока он не будет выполнен. Там работает только один "поток" JavaScript, хотя вы можете эффективно приостановить то, что делаете, пока не получите нужную вам информацию.

В приложениях С# то же самое происходит в случае, когда вы имеете дело с элементами пользовательского интерфейса - вам разрешено взаимодействовать с элементами пользовательского интерфейса, когда вы находитесь в потоке пользовательского интерфейса. Если пользователь нажал кнопку, и вы хотите ответить, прочитав большой файл с диска, неопытный программист может ошибиться в чтении файла в самом обработчике события клика, что приведет к "замораживанию" приложения до тех пор, пока файл завершил загрузку, так как ему не разрешалось отвечать на любые нажатия, зависания или любые другие события, связанные с пользовательским интерфейсом, до тех пор, пока этот поток не будет освобожден.

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

Если вы думаете о том, что делает CPU, когда он читает файл на уровне аппаратной/операционной системы, он в основном выдаёт инструкцию для чтения фрагментов данных с диска в память и попадает в операционную систему с помощью "прерывание", когда чтение завершено. Другими словами, чтение с диска (или любого ввода-вывода действительно) является по сути асинхронной операцией. Концепция потока, ожидающая завершения ввода-вывода, - это абстракция, которую разработчики библиотеки создали, чтобы упростить ее программирование. Это не нужно.

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

До тех пор, пока не были добавлены ключевые слова async/await, С# было гораздо более очевидным о том, как вызывается код обратного вызова, поскольку эти обратные вызовы были в форме делегатов, которые были связаны с задачей. Чтобы все еще использовать преимущества операции ...Async(), избегая сложностей в коде, async/await абстрагирует создание этих делегатов. Но они все еще существуют в скомпилированном коде.

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

Ответ 3

По определению асинхронности (оставляя текущий поток для выполнения чего-то еще), все многопоточные исполнения являются асинхронными, точка.

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

Ответ 4

Параллельная обработка

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

Процесс

  • Один процесс обычно связан с одним процессором.
  • ОС находится между процессором и процессом и распределяет разные процессы на разных процессорах
  • Процесс начинает выполняться из основного потока

Многопоточность

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

.Net Task Parallel Library

  • Фреймворки приложений, такие как tpl, обеспечивают абстракцию для многопоточности и параллельной обработки и оптимально распределяют ресурсы обработки с помощью собственных алгоритмов.
  • Когда tpl порождает задачу, он может или не может создать новый поток в том же процессоре или новый процесс в отдельном процессоре
  • Системные потоки управляются с помощью os, а управляемые потоки - с помощью clr/tpl

С# async/await Keyword

  • Асинхронные методы не требуют многопоточности, потому что асинхронный метод не выполняется в своем собственном потоке.
  • Async/await - это синхронный способ выполнения неблокирующих операций.
  • В веб-приложении Async обеспечивает масштабируемость, освобождая активный поток

Ответ 5

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