Как стать автором
Обновить

Как на самом деле Async/Await работают в C#. Уроки по асинхронному программированию из первой половины работы

Уровень сложностиСредний
Время на прочтение11 мин
Количество просмотров16K
Всего голосов 18: ↑13 и ↓5+8
Комментарии22

Комментарии 22

Почему-то никто не пишет, что Асинхронный вызов — это всегда использование концепции потоков (threads) реализованной Операционной Системой

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

Полностью поддерживаю. Очень странно видеть в тексте, который описывает понимание автором базовых вещей, принципиальную ошибку уровня новичка, в которой утверждается что Асинхронность всегда подразумевает Многопоточность. В том же С# концепция async/await подразумевает работу с тасками, которые все одновременно вполне могут работать как в одном потоке, так и в нескольких - как решит рантайм. В случае большого количества мелких тасков, генерирующих iobound, нет смысла запускать их в отдельных потоках, потому что создание потоков - удовольствие не из дешёвых. По уровню абстракции таски в C# примерно сопоставимы с горутинами в Go.

Даже немного страшно читать дальше.

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

Если это и ошибка, то это ошибка уровня СТАричка, а не новичка, потому что Многопоточность появилась и успешно применялась задолго до появления концепций по которым мы дискутируем (я надеюсь это то слово которое следует здесь применять).

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

Даже немного страшно читать дальше.

А я боюсь за этим может последовать: надо сжечь, желательно вместе с автором. Это мне что-то напоминает из совсем древних времен, ничего не меняется?

Это ошибка именно что новичка, в том смысле что её может совершить лишь тот, для кого асинхронность является новой областью, которую он ещё не знает.

Если же вы свои новичковые ошибки оправдываете тем, что вы якобы "старичок" - поздравляю, вы не "старичок", вы динозавр.

Статья на которую вы ссылаетесь, начинается оксюмороном:

 должен быть поток, в котором выполняется ожидание!

я поясню: словосочетание "выполнять ожидание" является внутренне противоречивым, вы не находите?

Далее автор той статьи доказывает что для выполнения ожидания потока нет, вопросы: а что он ожидал? А если рассмотреть операцию для которой все таки нужно производить какие то реальные вычисления асинхронно с вызывающим алгоритмом потока тоже не будет? Автор той статьи кроме операции ожидание, других операций не видел?

Но как я понимаю вас это нисколько не смущает. Ну хорошо, у меня появилась тема для новой статьи. Спасибо.

Почему вы эти вопросы задаёте тут, а не в комментариях к той статье?

я поясню: словосочетание "выполнять ожидание" является внутренне противоречивым, вы не находите?

Нет, не нахожу.

Далее автор той статьи доказывает что для выполнения ожидания потока нет, вопросы: а что он ожидал?

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

А если рассмотреть операцию для которой все таки нужно производить какие то реальные вычисления

А давайте всё-таки рассматривать операцию, для которой асинхронность придумывалась? Ежу понятно, что для вычислений нужен поток, проблема в том что некоторые пытаются что угодно свести к вычислениям.

Ежу понятно, что для вычислений нужен поток, проблема в том что некоторые пытаются что угодно свести к вычислениям.

Вот это интересно, вроде как компьютер ничего кроме как вычислений делать не умеет, оно же и переводится как "вычислитель". А выполнять ожидание вроде как значит "ничего не делать", то есть делать ничего-не-делание какое-то получается, в этом противоречивость, по моему. То есть мы насчитали 2 варианта:

1.вычислять

2.ничего не делать

или вы можете другие варианты предложить? только не надо предлагать варианты типа

ничего не делать (то есть ждать) того

ничего не делать (то есть ждать) этого

...

Рассмотрим следующий код (да, так писать нельзя, но как контрпример сойдёт):

bool flag;

// ...

while(!flag);

Что этот код делает? Ждёт.

Вычисляет ли этот код хоть что-то? Нет.

Занимает ли он поток? Да.

Занимает ли он процессор? Да.

вы хотите сказать что можно ничего не делать, при что-то делая?

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

Только я не понимаю к чему это вы, что это доказывает?

Я надеюсь это не вы минусы ставите, иначе глупо бы было вам что-то спрашивать. Вот в такой токсичной атмосфере приходится работать :( !

Я к тому, что все те, безусловно, очень тяжелые вычисления, перечисленные вами, обычные люди, не являющиеся троллями, называют "ожиданием". Более точно - активным ожиджанием.

Многопоточность появилась и успешно применялась задолго до появления концепций по которым мы дискутируем (я надеюсь это то слово которое следует здесь применять).

Так, подожди, на всякий случай спрошу. Ты понимаешь, чем многопоточность отличается от асинхронности? Просто если дискуссия продолжится, мне хотелось бы понимать, что она ведётся с человеком, который разбирается в вопросе.

А я боюсь за этим может последовать: надо сжечь, желательно вместе с автором.

Нет, конечно, я не сторонник чмырения за незнание. Если ты не знаешь и хочешь узнать, то мне не трудно накидать тебе статей по сабжу.

вот это, извините, хамство. Успеете пост удалить?

В чем заключается хамство?

Лучше бы вы и дальше просто переводили...

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

Нет никакого потока

Автор применил специальный термин для нее: «stack dives». Что такое stack dives? это же широко известное давно используемое понятие, это рекурсия! Проверьте меня, это всего лишь на всего рекурсия! Зачем вместо широко известного термина «рекурсия» изобретать какое-то вычурное «погружение в стек»? У меня есть одно предположение.

Потому что кто ему запретит? Вы вот точно всегда используете термины, или иногда заменяете их метафорами? Уверены, что я не найду ни одной метафоры если поищу по всем вашим публикациям?

А вы уже успели на основе одной метафоры сделать далеко идущие выводы...

В принципе этот новый класс контекста нужен (моя догадка) в том числе чтобы разрешить эту добавленную неопределенность относительно того является ли данная операция асинхронной (выполняемой в стороннем потоке – в контексте стороннего потока ИЛИ она выполняется в текущем потоке, а значит выполняется как синхронная). [...] Но насколько я понимаю, в конечном итоге SynchronizationContext будет представлять конкретный поток, который будет выделен для исполнения операции.

Вроде у автора все понятно написано, почему у вас какие-то странные догадки? Сначала написано что он делает, а потом - для чего это нужно. Но, судя по вашим цитатам, вы просто бросили читать на этом моменте.

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

Действительно, в начале Поста совершенно не понятно почему же автор так озаботился о том, а как же нам задать-передать функцию, которая будет вызываться после завершения асинхронной операции

Вот это вы серьёзно спрашиваете, почему же автор озаботился тем, чтобы получить и обработать результат операции?!

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

Дело в том, что эта функция, это «продолжение» играет ключевую роль в том, во что компилируется Async/Await (играет ключевую роль в построении реализации конструкций на основе Async/Await). 

Нет, не ставьте телегу впереди лошади. Продолжения не появились по воле авторов компилятора. Напротив, код компилируется именно так, как он компилируется, потому что нам нужны продолжения.

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

При создании через устаревший конструктор - да, представляют.

Но в современном использовании они представляют исключительно результат задачи. Аналогия с promise и future не случайна и полностью справедлива.

Чтобы эта полученная стейт машина могла работать, компилятор генерирует (или имеет встроенную) функцию, приблизительную версию кода которой нам демонстрирует автор Поста:

Ну уж нет, при компилятор генерирует напрямую конечный автомат для асинхронного кода. То, что привёл автор поста - лишь способ превратить yield return в await, компилятору такие костыли не требуются.

Нет никакого потока

На основании того, что я разобрал в работе, которой посвящена статья, я мог бы предположить что в примере

await myDevice.WriteAsync(data, 0, data.Length);

где автор НЕ обнаружил потока, асинхронная операция превратилась в синхронную.

Но...

Но Вы так уверены в ваших знаниях, что я не возьмусь даже пытаться вас переубедить.

 асинхронная операция превратилась в синхронную

С чего бы?

асинхронная операция превратилась в синхронную.

Тогда бы интерфейс завис(например в WPF)

Естественно если совсем уж уходить в глубину, то потоки есть, ОС же сама никуда не делась(с ее потоками). НО, там нет потоков созданных приложением. Системные потоки в таком случае не учитываются(например потоки для поддержания IO Completion Port).

Но, я трансформировал код аниматора на Android вместо колбэков на async/await, там даже системные потоки не использовались в итоге, все идет в одном UI потоке.(сам аниматор естественно тоже в UI потоке работает)

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

Кстати говоря, уже есть же цикл статей про async/await.

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

Кстати говоря, уже есть же цикл статей про async/await.

Да! у меня упоминается этот перевод уже есть на Хабре  под самым заголовком. На мой взгляд этот перевод - почти машинный перевод.

Тогда бы интерфейс завис(например в WPF)

Так там да, просто вызывающая функция распадается на кейсы под свичем.

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

Про SynchronizationContext я до конца не написал еще, возможно еще напишу.

Так там да, просто вызывающая функция распадается на кейсы под свичем.

Я знаю как это работает, я про превращение операции в синхронную.

На мой взгляд этот перевод - почти машинный перевод.

Она просто перевод, без догадок. Чем гораздо полезнее.

Но насколько я понимаю, в конечном итоге SynchronizationContext будет представлять конкретный поток, который будет выделен для исполнения операции.

Нет, нет и нет. SynchronizationContext - это абстракция, которая определяет специфику выполнения кода по завершению асинхронной операции (где, когда, с какими деталями). В .NET-е всегда было хорошее интуитивно-понятное именование типов. Почему в слове SynchronizationContext вы увидели поток? Правильно говорил С.Тепляков в одной из своих русскоязычных статей, что всех профессионалов объединяет одно - осторожность суждений. А вы даже в письменной форме в выражении своих мыслей умудрились накосячить. Не пахнет тут профессионализмом.

Вот почему я за то, чтобы читать статьи в оригинале, а не читать потом догадки переводчиков, со всеми их неправильными интуициями в сложных вопросах. НЕ НАДО продолжать публикации ваших догадок по статьям, описывающих сложные темы. Грамотные, опытные разработчики сами разберутся, что да как.
Как уже выше сказали, продолжайте просто переводить.
PS: И это только по одному абзацу замечание...

 И это только по одному абзацу замечание..

Что-то вы один абзац по которому есть замечание нашли уже ближе к концу. Но я этот абзац специально написал чтобы было к чему придраться! Меня убивает отсутствие комментариев - когда тихо как в могиле.

Поэтому, благодарю за замечание!

Что-то вы один абзац по которому есть замечание нашли уже ближе к концу.

Очевидные перлы ищутся быстрее, когда читаешь по диагонали. Нет желания вникать в вашу писанину, когда оригинал уже давно прочитан. Нужно быть очень хорошим переводчиком, чтобы грамотно переводить некоторые тонкие нюансы изложения у Stephen Toub. У вас этого не получилось.

Меня убивает отсутствие комментариев - когда тихо как в могиле.

Тема, на самом деле, уже давно изъезжена вдоль и поперек.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории