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

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

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

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

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

теперь я понимаю идею асинхронных задач, таких как пример на 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 никогда не используются в теле вышеуказанной функции.)

2 ответов


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

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

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

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

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

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

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

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


in-browser Javascript является отличным примером асинхронной программы, которая не имеет потоков.

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

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

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

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

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

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

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

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