Я опубликовал различные статьи об асинхронном ожидании, и я пытаюсь глубоко понять асинхронное ожидание.
Благородное занятие.
Моя проблема в том, что я обнаружил, что ожидание асинхронного метода не создает новую нить, а скорее просто делает пользовательский интерфейс отзывчивым.
Правильно.Очень важно понимать, что await
означает асинхронное ожидание . не означает "сделать эту операцию асинхронной".Это означает:
- Эта операция уже асинхронна .
- Если операция завершена, извлеките ее результат
- Если операция не выполненазавершите, вернитесь к вызывающей стороне и назначьте остаток этого рабочего процесса как продолжение незавершенной операции.
- Когда незавершенная операция завершится, будет запланировано выполнение продолжения.
Если это так, при использовании await async выигрыш во времени отсутствует, поскольку не используется дополнительная нить.
Это неверно.Вы не думаете о выигрыше времени правильно.
Представьте себе этот сценарий.
- Представьте себе мир без банкоматов.Я вырос в этом мире.Это было странное время.Поэтому в банке обычно есть люди, которые ждут, чтобы внести или снять деньги.
- Представьте себе, что в этом банке только один кассир.
- Теперь представьте, что банк принимает и выдает только однодолларовые банкноты.
Предположим, есть три человека.в очереди, и каждый хочет по десять долларов.Вы присоединяетесь к концу линии, и вам нужен только один доллар.Вот два алгоритма:
- Дайте первому человеку в строке один доллар.
- [сделайте это десять раз]
- Дайте второму человеку в строке одиндоллар.
- [сделайте это десять раз]
- Дайте третьему человеку в строке один доллар.
- [сделайте это десять раз]
- Дайте вамВаш доллар.
Сколько времени каждый должен ждать, чтобы получить все свои деньги?
- Человек один ждет 10 единиц времени
- Человек два ждет 20
- Человек три ждет 30
- Вы ждете 31.
Это синхронный алгоритм.Асинхронный алгоритм:
- Дайте первому человеку в строке один доллар.
- Дайте второму человеку в строке один доллар.
- Дайте третьему человекув строке один доллар.
- дайте вам свой доллар.
- дайте первому человеку в строке один доллар.
- ...
Это асинхронное решение.Теперь, сколько времени все ждут?
- Каждый, получающий десять долларов, ждет около 30.
- Вы ждете 4 единицы.
Средняя пропускная способность для больших рабочих мест ниже, но средняя пропускная способность для небольших рабочих мест намного выше . Это победа. Кроме того, время до первого доллара для каждого меньше в асинхронном рабочем процессе, даже если время до последнего доллара вышедля больших работ.Кроме того, асинхронная система ярмарка ;каждая работа ожидает приблизительно (размер задания) x (количество заданий).В синхронной системе некоторые задания почти не ждут времени, а некоторые - очень долго.
Другой выигрыш: кассиры стоят дорого;эта система нанимает одного кассира и получает хорошую пропускную способность для небольших работ.Как вы заметили, чтобы получить хорошую пропускную способность в синхронной системе, вам нужно нанять больше кассиров, что дорого.
Это также верно для Task.WhenAll () или Task.WhenAny ()?
Они не создают темы.Они просто берут кучу задач и завершают, когда все / любые задачи выполнены.
При создании задачи getStringTask другой поток скопирует текущий контекст и начнет выполнять метод GetStringAsync.
Абсолютно нет.Задача уже асинхронна , и, поскольку это задача ввода-вывода, ей не нужен поток.Оборудование ввода-вывода уже асинхронно.Новых рабочих не нанято.
В ожидании getStringTask мы увидим, завершил ли другой поток свою задачу
Нет, другого потока нет.Мы видим, выполнило ли оборудование IO свою задачу.Там нет темы.
Когда вы кладете кусок хлеба в тостер, а затем идете и проверяете свою электронную почту, в тостере нет человека, работающего с тостером .Тот факт, что вы можете запустить асинхронное задание, а затем уйти и заняться другими делами во время его работы, заключается в том, что у вас есть оборудование специального назначения, которое по своей природе асинхронно .Это верно для сетевого оборудования так же, как и для тостеров. Нет темы .Там нет крошечного человека, управляющего тостером.Он запускается сам.
, если не элемент управления вернется к вызывающей стороне метода AccessTheWebAsync (), пока другой поток не выполнит свою задачу для возобновления элемента управления.
СноваНет другой темы.
Но поток управления правильный. Если задача выполнена, то значение задачи извлекается.Если оно не завершено, управление возвращается вызывающей стороне после назначения оставшейся части текущего рабочего процесса как продолжения задачи. Когда задача завершена, продолжение планируется запустить.
я действительно не понимаю, как не создается никакой дополнительной нити при ожидании Задачи.
Опять же, подумайте о каждом разе в вашей жизни, когда вы перестали выполнять задачу, потому что были заблокированы, что-то сделалиеще какое-то время, а затем снова начал делать первое задание, когда тебя разблокировали. Вам пришлось нанять рабочего? Конечно, нет.Тем не менее, каким-то образом вам удалось приготовить яйца, пока тост был в тостере.Асинхронность, основанная на задачах, просто превращает этот реальный рабочий процесс в программное обеспечение.
Меня не перестает удивлять то, как ваши дети сегодня своей странной музыкой ведут себя так, будто потоки существуют всегда, и у нет другого способа сделать многозадачность.Я научился программировать в операционной системе, в которой не было потоков .Если вы хотели, чтобы две вещи происходили одновременно, вы должны были построить свою собственную асинхронность;это не было встроено в язык или ОС.И все же мы справились.
Совместная однопотоковая асинхронность - это возвращение к миру, каким он был до того, как мы допустили ошибку, представив потоки как структуру потока управления;более элегантный и гораздо более простой мир. Ожидание - это точка приостановки в кооперативной многозадачной системе. В Windows с предварительным потоком для этого нужно было бы позвонить Yield()
, и у нас не было языковой поддержки для создания продолжений и замыканий;вы хотели, чтобы состояние сохранялось через выход, вы написали код для этого.У всех вас это просто!
Может кто-нибудь объяснить, что именно происходит в ожидании Задачи?
Именно то, что вы сказали, просто без темы.Проверьте, выполнено ли задание;если это сделано, то вы сделали.Если нет, запланируйте оставшуюся часть рабочего процесса как продолжение задачи и вернитесь.Это все, что делает await
.
Я просто хочу кое-что подтвердить.Всегда ли случается, что при ожидании задачи нет созданного потока?
Мы беспокоились, когда проектировали функцию, чтобы люди поверили, как и вы до сих пор, что «ожидание» что-то делает с Позвоните , который идет после этого. Это не .Await что-то делает с возвращаемым значением .Опять же, когда вы видите:
int foo = await FooAsync();
, вы должны мысленно увидеть:
Task<int> task = FooAsync();
if (task is not already completed)
set continuation of task to go to "resume" on completion
return;
resume: // If we get here, task is completed
int foo = task.Result;
Вызов метода с ожиданием не является особым видом вызова. «Ожидание» не раскручивает поток или что-то в этом роде.Это оператор, который работает с возвращенным значением.
То есть в ожидании задания не раскручивает поток.Ожидание задачи (1) проверяет, завершена ли задача, и (2), если это не так, назначает оставшуюся часть метода как продолжение задачи и возвращает результат.Это все. Await ничего не делает для создания темы.Теперь, возможно, вызванный метод раскручивает поток;это это бизнес .Это не имеет ничего общего с await , потому что ожидание происходит только после возврата вызова. Вызываемая функция не знает, что ее возвращаемое значение ожидается .
Допустим, мы ожидаем задачу с привязкой к ЦП, которая выполняет сложные вычисления.До сих пор я знаю, что код, связанный с вводом / выводом, будет выполняться на низкоуровневых компонентах ЦП (намного ниже потоков) и использовать только потоки для краткого уведомления контекста о состоянии завершенной задачи.
Что мы знаем о вызове FooAsync выше, так это то, что он асинхронный и возвращает задачу.Мы не знаем, , как это асинхронно.Это автор бизнеса FooAsync!Но есть три основных метода, которые автор FooAsync может использовать для достижения асинхронности.Как вы заметили, есть два основных метода:
Если задача с высокой задержкой, потому что она требует длинных вычислений для текущей машины на другом процессоре, тогда имеет смыслполучить рабочий поток и запустить поток, выполняющий работу на другом процессоре.Когда работа закончена, связанная задача может запланировать свое продолжение для запуска в потоке пользовательского интерфейса, если задача была создана в потоке пользовательского интерфейса или в другом рабочем потоке, в зависимости от ситуации.
Если задача с высокой задержкой, поскольку требует связи с медленным оборудованием, таким как диски или сети, то, как вы заметили, нет потока.Специализированное оборудование выполняет задачу асинхронно, а обработка прерываний, предоставляемая операционной системой, в конечном итоге обеспечивает планирование выполнения задачи в правильном потоке.
Третья причина асинхронности - этоне потому, что вы управляете операцией с высокой задержкой, а потому, что вы разбиваете алгоритм на маленькие части и помещаете их в рабочую очередь.Возможно, вы создаете свой собственный планировщик, или внедряете систему модели актера, или пытаетесь программировать без стеков, или что-то еще.Нет потока, нет ввода-вывода, но есть асинхронность.
Итак, опять-таки, в ожидании не заставляет что-то выполняться в рабочем потоке. При вызове метода, запускающего рабочий поток, что-то запускается в рабочем потоке .Пусть метод , который вы вызываете, решает, создавать ли рабочий поток или нет. Асинхронные методы уже асинхронны .Вам не нужно ничего с ними делать, чтобы сделать асинхронными.Await не делает ничего асинхронного.
Ожидание существует исключительно для того, чтобы разработчику легче было проверить, завершена ли асинхронная операция, и зарегистрировать остаток текущего метода в качестве продолжения, если он еще не завершен.Вот для чего это.Опять же, await не создает асинхронность .Await помогает создавать асинхронные рабочие процессы . Ожидание - это точка в рабочем процессе, где асинхронная задача должна быть завершена, прежде чем рабочий процесс может быть продолжен .
Я также знаю, что мы используем Task.Run () для выполнения ЦПсвязанный код для поиска доступного потока в пуле потоков.Это правда?
Это верно.Если у вас есть синхронный метод, и вы знаете, что он привязан к процессору, и вы хотите, чтобы он был асинхронным, и вы знаете, что метод безопасен для запуска в другом потоке, тогда Task.Run найдет рабочий поток, расписаниеделегат, который будет выполнен в рабочем потоке, и даст вам задачу, представляющую асинхронную операцию.Вы должны делать это только с методами, которые (1) очень долго работают, например, более 30 миллисекунд, (2) связаны с процессором, (3) безопасны для вызова в другом потоке.
Если вы нарушите что-либо из этого, произойдут плохие вещи.Если вы нанимаете работника для выполнения работы менее 30 миллисекунд, подумайте о реальной жизни.Если у вас есть какие-то вычисления, имеет ли смысл покупать рекламу, проводить собеседования с кандидатами, нанимать кого-то, заставлять их добавлять три десятка номеров вместе, а затем увольнять их? Наем рабочего потока стоит дорого .Если нанимать нить дороже, чем просто выполнять работу самостоятельно, вы не получите никакого выигрыша в производительности, нанимая нить;Вы сделаете это намного хуже.
Если вы нанимаете работника для выполнения задач, связанных с вводом-выводом, то, что вы сделали, это наняли работника, который будет сидеть у почтового ящика в течение лет и кричать при получении почты. Это не заставляет почту приходить быстрее .Это просто напрасно тратит ресурсы рабочего, которые могут быть потрачены на другие проблемы.
И если вы нанимаете рабочего для выполнения задачи, которая не является поточно-ориентированной, хорошо, если вы нанимаете двух рабочих и говорите им, чтобы оба ездили на одной машинев двух разных местах одновременно, они собираются разбить машину, пока они борются за руль на автостраде.