Изучаю асинхронное программирование и вижу следующий метод
async Task Produce(ITargetBlock<string> queue, int howmuch)
{
Random r = new Random();
while (howmuch-- > 0)
{
await Task.Delay(1000 * r.Next(1, 3));
var v = string.Format("automatic {0}", r.Next(1, 10));
await queue.SendAsync(v);
}
queue.Complete();
}
Что-то не так с этим методом, но что не могу понять. Кажется, async
и await
лишние.
Непонимаю для чего надо вызывать await Task.Delay
? Про то, что это для иммитации бурной деятелности это понятно.
Вопрос о другом: если надо остановить текущий поток, то для чего запускать другой поток?
Почему не сделано просто: Task.Delay(1000 * r.Next(1, 3)).Wait();
?
Если вызвали Delay, то значит надо подождать указанное время, то есть надо остановить текущий поток. А из-за await получается какая-то ерунда, так как поток вызывает другой поток, чтобы ждать в нем, и сам ждет другой поток.
Если метод возвращает Task
, то почему нет return
?
Давайте начнем с того, зачем вообще появилась нужда в async/await
. Представим, в приложении есть сетевой вызов, занимающий время. Или нужно записать большой файл на диск. Секрет в том, что в тот момент, когда вызов уходит на устройство (будь то сетевая карта или жесткий диск), текущий поток блокируется до тех пор, пока не придет ответ (т.е. пока не придет ответ от сервера или все данные не сбросятся на диск). Это расточительное использование ресурсов, поскольку в это время текущий поток ничего не делает, но мог бы заниматься другой работой. Например, на это графике видно, какую производительность выдает некое серверное приложение без использования async/await
и с использованием async/await
. Оба приложения ограничены 50-ю потоками:
Видно, что как только начинает приходить больше 50-ти одновременных запросов, синхронное приложение начинает отвечать хуже, потому что потоки по большей части заняты бесполезным ожиданием. Асинхронное же приложение продолжает нормально отвечать на запросы, потому что потоки все время работают и даже 50-ти потоков хватает, чтобы обслужить 100 клиентов без потери во времени отклика.
Представьте себе аналогию: ресторан -- это ваше приложение, официанты -- это потоки в приложении, клиент за столиком -- это запрос. В случае синхронного приложение происходит вот что:
Фактически получается та же самая ситуация -- на каждого клиента нужен свой официант. Абсурд! Вы тратите лишние деньги на зарплату людям, которые бОльшую часть времени ничего не делают. Точно так же ОС тратит лишние ресурсы на потоки, которые блокируются в ожидании.
В правильном же ресторане официант занят только на подаче меню, приеме заказа, подаче блюда и уборке. В остальное время он не простаивает в ожидании, а обслуживает других клиентов. Например, один официант может одновременно обслуживать пять столиков. Так и в асинхронном приложении небольшое количество потоков обслуживает большое количество запросов.
Да, async
и await
являются всего лишь ключевыми словами в языке, т.е. по сути служат всего лишь некоторыми указаниями для компилятора.
Ключевое слово async делает три вещи:
await
Ключевое слово await делает две вещи:
Т.е. никакого отношения к потокам эти два слова не имеют.
Для более детального ликбеза могу посоветовать вам посмотреть вот это выступление или хотя бы слайды, где (я надеюсь :)) доступно и на пальцах изложено, как работает async/await
, а также разобраны основные заблуждения (коими и наполнен вопрос).
Сперва выполняет часть метода до первого await
:
Random r = new Random();
while (howmuch-- > 0)
{
await Task.Delay(1000 * r.Next(1, 3));
Затем начинает ожидание, а текущий поток покидает метод и используется CLR для чего-то другого. Если это был UI поток, то он пойдет обрабатывать message loop.
Ожидание завершается. Следующая часть кода начинает выполняться в том же контексте, в котором выполнялась предыдущая часть. Это значит, что если до этого у нас был UI контекст, ASP.NET контекст, или любой другой однопоточный контекст, то продолжение будет выполнено в том же потоке, что и предыдущая часть. Если же код выполнялся в многопоточном контексте (например, в пуле потоков), то тут уже гарантий никаких нет -- это может быть тот же поток, а может быть и нет:
var v = string.Format("automatic {0}", r.Next(1, 10));
await queue.SendAsync(v);
Как только метод SendAsync()
внутри себя примет запрос, он вернет управление, наш поток в свою очередь снова выйдет из текущего метода.
Через некоторое время метод SendAsync()
завершится, и наш метод снова продолжит работу.
Теперь давайте пройдемся по конкретным вопросам:
Непонимаю для чего надо вызывать await Task.Delay? Про то, что это для иммитации бурной деятелности это понятно.
Этот вопрос на самом деле надо задавать автору кода. Возможно, имитация, а возможно и искусственное ограничение, чтобы на учебном примере было видно, как работают производитель и потребитель.
Вопрос о другом: если надо остановить текущий поток, то для чего запускать другой поток? Почему не сделано просто: Task.Delay(1000 * r.Next(1, 3)).Wait();?
Предложенная вами реализация будет занимать поток. Поток 1-3 секунды не будет занят ничем полезным, кроме ожидания. При использовании await
поток на время ожидания будет свободен и может быть использован для другой работы. А когда время ожидания истечет (ожидание при этом хитрым образом делается на уровне CLR/ОС с использованием системных таймеров и ресурсов практически не требует), то выполнение метода будет продолжено. И, как уже было сказано выше, не факт, что в "другом потоке".
Если вызвали Delay, то значит надо подождать указанное время, то есть надо остановить текущий поток.
Как я уже сказал, текущий поток останавливать совсем не обязательно. Task.Delay()
как раз реализует такое ожидание, которое не требует занятия отдельного потока. А текущий поток в это время может заняться другой работой.
А из-за await получается какая-то ерунда, так как поток вызывает другой поток, чтобы ждать в нем, и сам ждет другой поток.
Без комментариев :). Если вы прочитали и поняли все, что я уже написал выше, а тем более посмотрели видео, то отвечать на это подробно уже нет нужды. Сами сможете ответить.
Если метод возвращает Task, то почему нет return?
Как я уже сказал выше, модификатор async
в т.ч. говорит компилятору, что данный метод нужно специальным образом скомпилировать. Побочным эффектом специальной компиляции и является тот факт, что вместо Task
можно возвращать void
, а вместо Task<T>
-- T
.
Кажется, async и await лишние.
Если делать все правильно, то нет, не лишние.
А из-за await получается какая-то ерунда, так как поток вызывает другой поток, чтобы ждать в нем, и сам ждет другой поток.
Вы не совсем верно представляете себе механизм await. Ваш код вообще не обязательно запускает дополнительные потоки. Причина в том, что асинхронность и многопоточность - это две разные вещи. Например, javascript асинхронен - в нем все сетевые операции подразумевают callback по завершению. Но при этом он полностью однопоточен - код в нем выполняется без распараллеливания.
Что реально происходит в вашем коде:
Компилятор режет код на две части:
Часть A:
async Task Produce(ITargetBlock<string> queue, int howmuch)
{
Random r = new Random();
while (howmuch-- > 0)
{
Часть B:
var v = string.Format("automatic {0}", r.Next(1, 10));
await queue.SendAsync(v);
}
queue.Complete();
}
Между ними находится какое-то долгое действие, результата которого можно ждать, не нагружая проц (по крайней мере в текущем потоке). В вашем случае это Task.Delay
. В реальном случае это или долгая сетевая/дисковая операция, или явно запущенный в отдельном потоке код (Task.Run
).
Что происходит при выполнении этого кода, например, в WinForms:
Основной профит:
Попробуйте добиться того же без использования async/await.
В случае ASP.NET нет UI потока, и профит от использования async/await заключается в освобождении потока на время ожидания долгой операции, что позволяет выполнять чуть больше одновременных запросов, не оставляя потоки висеть в ожидании.
Кажется, async
и await
лишние
если их убрать, то метод станет синхронным. Если вам нужен именно асинхронный метод, то async
и await
явно не лишние.
Не понимаю для чего надо вызывать await Task.Delay
?
вероятно, это демонстрационный пример, и вызовом Delay
имитируются некие долгие вычисления
Если метод возвращает Task
, то почему нет return
?
async/await
методы, возвращающие Task
, являются асинхронными аналогами для не асинхронных методов, возвращающих void
. В них можно не указывать return
явно.
Почему не сделано просто: Task.Delay(1000 * r.Next(1, 3)).Wait();
?
потому что тогда никакой асинхронности не получится. Ваш метод в этом случае просто "повиснет" на период r.Next(1, 3)
секунд, ожидая завершения Task.Delay
, после чего продолжит выполнение. В приведенном же вами коде метод, достигнув await, приостановит выполнение до завершения Task.Delay
и вернет управление вызвавшему его коду, который тем временем сможет заняться чем-то более полезным, чем ожидание. Когда задача завершится, будет выполнен "остаток" метода после Task.Delay
- своеобразный коллбэк без явного указания функции обратного вызова.
По приведённой @Grundy ссылке есть ответ Эрика Липперта с очень хорошей аналогией про официанта и заказ в ресторане, почитайте, многое встанет на свои места
Давайте рассмотрим все по порядку, без лишних слов и так чтобы было понятно даже новичкам.
1) Метод скопирован из ответа VladD https://ru.stackoverflow.com/a/431145/201561
2) Метод используется в программе для TPL Dataflow (надстройка над Task'ами).
3) В методе присутствует цикл с ожиданием, то есть при каждом вызове цикла надо просто подождать, как это видно из следующего фрагмента метода.
while (howmuch-- > 0)
{
await Task.Delay(1000 * r.Next(1, 3));
var v = ...
В соседних ответах говорят, что когда поток доходит до await, то он освобождается для выполнения другой работы.
Это правильно, но возникает вопрос: какой работы?
Чтобы понять о какой другой работе идет речь, надо вспомнить о TaskScheduler и о Task'ах.
Task'и можно сравнить с покупателями, которые стоят в очереди к кассам.
В качестве кассира выступает Thread из пула потоков.
TaskScheduler распределяет Task'и между кассами.
Каждый Task дойдя до кассы отдает кассиру свой делегат. И Thread вызывает метод, соответствующий делегату.
В случае когда в методе указан await, то компилятор создает два делегата.
То что находится в коде до await оказывается в делегате #1, а остальное - в делегате #2.
Кассир вызывает делегат #1 и на этом его работа с этим Task заканчивается, и Thread освобождается для другой работы, со следующим Task.
Что же проиходит с Delay и с делегатом #2?
Происходит следующее: на какой-то другой свободной кассе вызывается Delay, что приводит к созданию еще одного Task, и запускается Timer.
Через указанный промежуток времени Timer вызывает завершение Task, а затем вызывается делегат #2.
Очевидно, что в данной ситуации нет необходимости в создании #1 и #2, также не нужно задействовать разные Thread.
Вывод: если в текущем потоке надо просто подождать, то async/await не нужен.
если коротко то без async не возможен await, а без await не возможен вызов асинхронной операции записи в очередь queue.SendAsync ради которой метод и написан.
Можно использовать так же .Result у таск, и это избавит от await/async Но синхронизация с вызывающим методом уже будет происходить по другому. В этом случае управление не будет передано и приемущество асинхронности будет лишь в том коде который между вызовом GetAsync и .Result. В вашем случае без await не произойдет перехода в другой поток и delay будет стопорить главный.
Если есть синхронная копия queue.Send то можно воспользоваться ей. Но в этом случае не будет задержка изза синхронизации.
Следует разлечать потоковые операции которые и так по своей природе асинхронные, и операции с ресурсами, которые МОГУТ(а могут и нет) требовать тупого ожидания ответа, ради них и был разработан механизм. До появления async приходилось пользоваться потоками или мудрить с колбеками для того чтобы разгрузить главный поток управления от ожиданий что невероятно усложняло код.
В качестве иллюстрации код ниже при вызове в самом начале консольного приложения будет печатать букву x. ПРи этом будут выполняться все остальные команды приложения вврод выдор расчеты. Если убрать async/awaint очевидно что это вечный цикл и программа будет печатать букву и больше ничего не делать .
async static Task Updater()
{
while (true)
{
await Task.Delay(100);
Console.Write("x");
}
}
Виртуальный выделенный сервер (VDS) становится отличным выбором
Есть вот такой скрипт:
Есть ли возможность отловить момент воспроизведения определённого звука на компьютера?
Всем доброго дня! Хочу использовать SQLite в своем проекте, защитив ее паролемВ гугле нашел такие решения: