71

Я думал, что они в основном то же самое - написание программ, разделяющих задачи между процессорами (на машинах с 2+ процессорами). Затем я читаю https://msdn.microsoft.com/en-us/library/hh191443.aspx, в котором говорится:В чем отличие асинхронного программирования от многопоточности?

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

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

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

Теперь я понимаю идею асинхронных задач, таких как пример на pg. 467 из Джона Скита C# в глубину, третье издание

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 никогда не использовать в теле функции выше.)

+9

Это приятное объяснение: http://blog.stephencleary.com/2013/11/there-is-no-thread.html –

+0

Ссылка действительно хорошая, взгляните на нее. – Ian

+0

'sender' и' e' предполагают, что это на самом деле обработчик событий - в значительной степени единственное место, где желательно «асинхронное void». Скорее всего, это называется нажатием кнопки или что-то в этом роде - результатом является то, что это действие происходит полностью асинхронно по отношению к остальной части приложения. Но все равно все в одном потоке - поток пользовательского интерфейса (с крошечной лентой времени в потоке IOCP, который отправляет обратный вызов в поток пользовательского интерфейса). – Luaan

ответ

194

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

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

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

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

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

Так что давайте рассмотрим пример Джона более подробно. Что происходит?

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

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

+1

На аппаратном уровне, что делает асинхронная задача, тогда? Это просто заставляет компилятор думать о том, как лучше изменить программу? Например, если у вас есть 'P (async); Q;', то он будет переупорядочивать задачи до 'Q; P;', или он будет делать что-то вроде 'Немного P; Немного Q; Немного Р; и т. д. В чем смысл? Я не понимаю ... – user5648283

+4

@ user5648283: Аппаратное обеспечение - это неправильный уровень, чтобы думать о задачах. ** Задача - это просто объект, который (1) означает, что значение станет доступным в будущем, и (2) может запускать код (в правильном потоке), когда это значение доступно **. Как любая индивидуальная задача получает результат в будущем, зависит от нее. Некоторые из них будут использовать специальное оборудование, такое как «диски» и «сетевые карты»; некоторые из них будут использовать аппаратные средства, такие как процессоры. –

+5

@ user5648283: Опять подумайте о моей аналогии. Когда кто-то просит вас готовить яйца и тосты, вы используете специальное оборудование - плиту и тостер, и вы можете убирать кухню, пока аппаратура выполняет свою работу. Если кто-то спросит вас о яйцах, тосте и оригинальной критике последнего фильма Хоббит, вы можете написать свой отзыв, пока яйца и тосты готовят, но вам не нужно использовать оборудование для этого. –

8

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

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

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

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

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

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

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

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

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

+0

* Работает только один JavaScript-поток * - больше не работает с [Веб-работниками] (https://html.spec.whatwg.org/multipage/workers.html). – oleksii

+1

@oleksii: Это технически верно, но я не собирался заниматься этим, потому что сам API веб-рабочих является асинхронным, а веб-работникам не разрешено напрямую влиять на значения JavaScript или DOM на веб-странице, вызывается из, что означает, что ключевой второй абзац этого ответа по-прежнему сохраняется. С точки зрения программиста, существует небольшая разница между вызовом веб-рабочего и вызовом запроса AJAX. – StriplingWarrior

Смежные вопросы