it-swarm.com.ru

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

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

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

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

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

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

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

149
user5648283

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

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

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

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

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

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

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

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

387
Eric Lippert

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

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

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

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

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

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

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

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

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

15
StriplingWarrior