Читаю вот эту статью про использование Task и async-await в C#
В ней приведён такой код
var client = new WebClient();
var task = client.DownloadStringTaskAsync("/api/blabla");
Console.WriteLine("Hello world!");
var result = await task;
Console.WriteLine("Got some data");
и далее про него говорится вот что
Почему этот код хороший и правильный? Потому что DownloadStringTaskAsync возвращает Task, который инкапсулирует операцию ввода-вывода — то есть I/O bound операцию. И практически весь ввод-вывод является асинхронным — то есть, для его осуществления, нигде, начиная с самого верхнего уровня вызова метода DownloadStringTaskAsync и заканчивая драйвером сетевой карты, абсолютно нигде не нужен дополнительный поток, который будет «ждать» или «обрабатывать» эту операцию.
Точно так же приводится пример из JS/jQuery
$.get('/api/blabla', function(data) {
console.log("Got some data.");
});
console.log("Hello world!")
и комментарий к нему
Это — очень яркий пример асинхронного программирования. Здесь нет никакой многопоточности — Javascript строго однопоточен
В обоих случаях особо отмечается что многопоточности тут нет. И подобное я читал также и в других источниках что при использовании async-await (и асинхронности вообще) далеко не всегда подразумевает использование дополнительного потока. И это мне совершенно непонятно. Возьмем тот же пример на jQuery. Сначала выполняется запрос, у которого есть коллбэк а потом выводится сообщение "Hello world!". Но так как запрос выполняется асинхронно то "Hello world!" выводится раньше. И мне непонятно как тут обойтись без нескольких потоков? Ведь если асинхронный запрос выполняется в одном потоке с основным, то по идее и асинхронности быть не должно потому что поток сначала должен дождаться выполнения запроса а только потом вывести
"Hello world!". То же самое и по C# коду. Объясните пожалуйста почему автор так упорно говорит что многопоточности тут нет? Заранее спасибо!
Ответ
Дело в том, что сетевые и дисковые вызовы в Windows поддерживают так называемый Overlapped I/O, который позволяет получить уведомление по завершению операции. Т.е. во время выполнения операции с диском или сетью пользовательский поток может делать все что угодно, а не обязательно спать и ждать результатов.
Как работает обычное, неасинхронное чтение:
Ваш код делает синхронный вызов
Поток, сделавший вызов, засыпает
Данные читаются из диска или из сети
Поток просыпается
Вызов возвращает результат
Как работает асинхронное чтение
Ваш код делает асинхронный вызов
Поток, сделавший вызов, освобождается - возвращается в пул
Данные читаются из диска или из сети
Приложение получает уведомление от системы о получении данных (через I/O Completion Port)
Рантайм находит код, который должен обработать результат - продолжение вашего async-метода и ставит его на выполнение.
Ваш код возвращает результат
Разница в том, что между 2 и 6 в асинхронном коде никакой поток не спит в ожидании результата. А потоки в Win - это достаточно ценный ресурс. Их тяжело (относительно тяжело) запустить. Они требуют памяти (около 1Mb на поток). Их надо синхронизировать - чем больше потоков, тем больше накладных расходов.
Кроме того, если между 2 и 6 в вашем коде есть что-то, что можно выполнить (например, вы не сразу потребовали результат чтения с диска или из сети) - то это будет выполнено потоком, вместо пустого ожидания. При этом будет одновременность выполнения вашего кода, и физического чтения данных из сети.
Но многопоточности все еще не будет - поток то у вас останется ровно один. То, что при этом драйвер сетевой карты читает данные - не добавляет еще один поток в ваше приложение. И вы можете спокойно обойтись без синхронизации, и прочих неприятностей, связанных с многопоточностью.
Абстракция, как же без нее:
многопоточное изготовление пиццы - склонировать себя и заставить клона готовить пиццу. по завершению - слиться в одного человека.
асинхронное изготовление пиццы - позвонить в доставку, спокойно делать свои дела, по звонку в дверь открыть и забрать готовую пиццу.
Комментариев нет:
Отправить комментарий