thedeemon: (passport)
[personal profile] thedeemon
На языках программирования можно ввести отношение частичного порядка "А < Б", когда с языка А на язык Б пересесть можно, и это ощущается как прогресс, а вот обратно возвращаться очень неохота и мучительно. Если не ошибаюсь, [livejournal.com profile] lionet в свое время заметил, что в этом отношении заметны две вершины - хаскель и лисп, с их высоты все остальные языки кажутся недостаточно хорошими. Но занятно другое: у этого отношения есть также два дна, по сравнению с которыми все другие языки выглядят превосходными, - это PHP и C++. :)

После ряда других языков мне заставить себя писать что-то на С++ очень сложно, но иногда такая необходимость возникает. Помогает сгладить моральные мучения лишь возможность найти в языке крупицы чего-то хорошего. Нынче вот взялся за новую реализацию своего фирменного super resolution движка, и что меня сейчас радует и выручает, это присутствующие в языке элементы зависимых типов. У меня код оперирует блоками разных размеров и векторами разной точности: это могут быть целые координаты в кадре низкого разрешения, в кадре высокого разрешения, а также координаты с полупиксельной и четвертьпиксельной точностью. Плюсовые шаблоны позволили описать эти вещи как семейства типов, индексированные целочисленными значениями, т.е. натурально зависимые типы получились:
#define VP_LOWRES  1
#define VP_HIRES   2
#define VP_HALF    3
#define VP_QUARTER 4

template <int Prec>
class Vec
{
public:
    int x, y;

    Vec(int vx, int vy) : x(vx), y(vy) {}
    Vec operator+(Vec<int Prec> &a) { return Vec<Prec>(x + a.x, y + a.y); }
    Vec operator-(Vec<int Prec> &a) { return Vec<Prec>(x - a.x, y - a.y); }
    Vec<Prec + 1> refine()          { return Vec<Prec + 1>(x*2, y*2); }
    ...
};

class Plane
{
    ...
    template<int W> void readBlockQP(Vec<VP_QUARTER> v, MonoBlock<W> &block);
    ...
}

В результате блоки разного размера - это разные типы, и векторы разной точности - разные типы, реально очень помогает не запутаться. Плюс компилятору подспорье: у многих циклов число итераций теперь известно статически, можно хорошо оптимизировать. В иных языках для таких вещей можно использовать phantom types, но там может быть сложнее сделать функцию refine, переводящую вектор на следующий уровень точности, в соседний слой семейства. Все-таки очень удобно, когда с параметром типа можно делать всякую арифметику и использовать его сразу на двух уровнях: типов и выражений.

К слову о зависимых типах. Одна тривиальная мысль о них мне лично оказалась весьма полезной для понимания. Мы привыкли в функциональных языках обозначать тип функции из А в В как А -> B, где А и В какие-то конкретные типы вроде Bool и Int, элементами которых служат значения вроде true и 2. Теперь добавим в систему типов еще один тип, назовем его Type, элементами которого являются типы. Тогда A -> Type будет просто типом функции, которая каждому элементу А сопоставляет какой-то тип. Это и получится зависимый тип, и именно так он и обозначается в соответствующих языках. Например, пишут B : A -> Type и говорят, что В - это зависимый от А тип, или семейство типов, индексированное значениями из А. Но эту же запись можно воспринимать и буквально - как обычную функцию, просто кодомен у нее не совсем обычный.

Date: 2013-05-07 11:29 pm (UTC)
From: [identity profile] soonts.livejournal.com
>Ищи LINQ здесь (статья 2009 года)
Концепции и парадигмы — это для архитектурных астронавтов термины, я ими не пользуюсь.
Реализация Linq2SQL появилась в Haskel на 4+ года позже, чем в C#.

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

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

>ничего общего не имеет с элегантностью языка. Практично? Да. Удобно? Да.
Именно это я и называю элегантностью.
Язык программирования — это не произведение искусства, а инструмент для решения реальных задач.

>В C# тоже с этим не всё так хорошо как в лиспе, например.
Лисп даже не умеет традиционного статически типизированного ООП.

Date: 2013-05-08 07:38 am (UTC)
From: [identity profile] dmytrish.livejournal.com
В том и элегантность, что огромную кучу сложной, но необходимой логики спрятали за двумя несложными для понимания ключевыми словами. — а зачем вообще нужна эта вся сложность, если то же самое делается в упомянутых языках намного проще? И кроме того, позволяет ли async/await эту сложность переиспользовать, комбинировать, природно порождать новые конструкции языка?

Язык программирования — это не произведение искусства, а инструмент для решения реальных задач. — с этого я и начинал. Но тем самым вы ограничиваете себя именно кругом «реальных задач», это и есть «зона комфорта» C#, в то время, как более развитые языки (H. и L.) обладают существенной гибкостью, которая позволяет им неплохо себя чувствовать in the wild, в поле, в котором еще никто подобных задач не решал (и откуда и приходят все фичи, которыми так гордится сообщество C#, похоже, не подозревая о существовании хотя бы Microsoft Research, словно их дают незримые майкрософтовские боги — именно это подразумевалось под «пользователь C# сам эту сложность соорудить не в состоянии», у него есть возможность, потенциальность, но откуда возьмутся идеи?). Вы и не подозреваете, сколько прекрасных идей и инструментов все еще нет в C# и не будет.

Лисп даже не умеет традиционного статически типизированного ООП. — Clojure очень даже умеет, поскольку она работает поверх JVM, вот только сами кложуристы избегают тыкать палочкой в goop без особой необходимости. Думаю, соорудить Java-style велосипед вполне возможно и в Common Lisp, но зачем?!

Date: 2013-05-08 05:54 pm (UTC)
From: [identity profile] soonts.livejournal.com
>то же самое делается в упомянутых языках намного проще?
Вообще не делается. Вот вам к примеру метод на C#, который принимает 100 байт из сокета, на каждый байт шлёт в ответ XOR всех ранее полученных байт:

async Task DoThat( Socket s )
{
	byte res = 0;
	for( int i = 0; i < 100; i++ )
	{
		byte b = await s.ReadByte();
		res ^= b;
		await s.WriteByte( res );
	}
}


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

>позволяет ли async/await эту сложность переиспользовать, комбинировать
Да.

>природно порождать новые конструкции языка?
Нет и оно не нужно, поскольку код с "порождёнными новыми конструкциями языка" крайне сложно читать и поддерживать.

>Вы и не подозреваете, сколько прекрасных идей и инструментов все еще нет в C# и не будет.
Список в студию.

Date: 2013-05-08 07:28 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
"async Task DoThat( Socket s )"

но это же вы из non-blocking IO в три строки сделали blocking IO. Как бы, понятно всё, не понятно только, зачем гвозди забивать табуреткой :)

я надеялся увидеть async, делающий map, и await, делающий reduce.


"занятый поток на время ожидания"

во, а вот это вот место я и хотел в другой ветке расспросить. Вот у вас await s.Read(). В этом месте поток, вызывающий метод, делает что? context switch? Поток зелёный или нативный? Если нативный, то получается "занятый поток на время ожидания".
Edited Date: 2013-05-08 07:32 pm (UTC)

Date: 2013-05-08 07:53 pm (UTC)
From: [identity profile] soonts.livejournal.com
>в три строки сделали blocking IO
Нет не сделал.
Пока await ждёт, ни один поток этим не занят.
Таких одновременно работающих тасков могут быть тысячи на разных сокетах, это вообще не уронит performance, как было бы в случае blocking IO.

>async, делающий map, и await, делающий reduce
async-await не нужен для CPU bound тасков.
map-reduce по-идее элементарно делается на TPL (я думаю надо начать с чтения MSDN по вопросу Parallel.ForEach), но гуглите сами, я этот кусок фреймворка не использовал.

>делает что?
Что угодно ещё на усмотрение task scheduler’а.
Иногда например (если данные уже в буфере сокета, а работающих тасков меньше чем ядер) он сразу вернёт байт и продолжит в этом же таске.
Иногда переключится на другой таск.
Иногда уснёт до появления или новых тасков которые нужно исполнять или данных в сокете.
Edited Date: 2013-05-08 08:18 pm (UTC)

Date: 2013-05-09 07:17 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
без зелёных ниток не обойтись.

Date: 2013-05-09 08:15 am (UTC)
From: [identity profile] soonts.livejournal.com
Является ли async-await зелёными нитками?

Date: 2013-05-09 08:48 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
Я щас попробую объяснить.

int blah = async compute();

В месте вызова compute() кто-то должен сохранить состояние функции-вызыватора. Куда? Без async всё складывается в железный стек. С async всё, что лежит на стеке, должно уйти куда-то ещё - в мягкий стек; мы же хотим, чтобы железный поток не простаивал, а занимался делом.

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


2. Раньше было:

compute();
finish();

Теперь кто-то эти методы может вызвать async. Раньше у вас были головняки со state transition гарантированным program order, теперь у вас головняки с concurrency in practice, где ничего такого не было. Как пример, запись в поток в цикле - речь не обязательно о файловом потоке, подумайте о шифровании или сжатии. Если функция-вызыватор сама не позаботится о гигиене использования потока, вместо нормального цикла получится асинхронный ад с out of order заполнением потока.


3. async Task<blah> oops() { async oops() } - интересно, что переполнится в этом случае и какова диагностика? Тут ещё вариант, что я не понял, как правильно выразить эту мысль - но должно же быть возможно выразить бесконечную рекурсию. Диагностика этого не то, чтобы невозможна, но хотелось бы услышать, как это решается в данном езыке.

Date: 2013-05-09 09:19 am (UTC)
From: [identity profile] soonts.livejournal.com
>куда-то ещё - в мягкий стек
В кучу уходит, не в стек.

>кто-то эти методы может вызвать async
Этот кто-то .NET framework.

>теперь у вас головняки с concurrency in practice
Я написал довольно много async-await кода (начинал году в 2010 с Async CTP). Этот подход как раз решает эти ваши "головняки с concurrency in practice", а не создаёт. Очень часто удаётся написать даже очень нетривиальный параллельный код без единой блокировки, только с использованием гарантий, которые дают компилятор и рантайм.

>Если функция-вызыватор сама не позаботится о гигиене использования потока
Ничего не нужно делать.

>вместо нормального цикла получится асинхронный ад с out of order заполнением потока.
Не получится, за вас framework это сделает. Framework гарантирует, что statements в async методе будут исполняться строго последовательно, хотя и не обязательно одним потоком конечно.
А может быть и одним потоком, поведение зависит от того есть ли synchronization context, framework сам умеет при необходимости маршалить вызовы в тот поток, из которого вызвали await.

>async Task oops() { async oops() }
Имеете в виду видимо
async Task oops() { await oops(); }?
Получите обычный stack overflow exception в рантайме.

>должно же быть возможно выразить бесконечную рекурсию
Элементарно.
async Task oops() { await Task.Yield(); await oops(); } - ничего не будет падать, и получите бесконечную рекурсию, только зачем?
Edited Date: 2013-05-09 09:25 am (UTC)

Date: 2013-05-09 12:12 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
"В кучу уходит, не в стек."

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


"Этот подход как раз решает эти ваши "головняки с concurrency in practice", а не создаёт."

я думаю, моя мысль ускользнула. Пример на гугле показывает как раз головняки с concurrency in practice, а ваш пример с await s.readByte() ничего интересного не делает. Интересное как раз вот в чём (по памяти пример из гугла):

Task<string> getString = connection.getStringAsync("http://..."); -- здесь, я так понимаю, не только async таск создаётся, но и запускается. это fork
DoSomethingIndependent(); -- Вот это вот и есть concurrency in practice
return await getString; -- это join

Вызыватор должен быть в курсе, где там внутри методов есть async, иначе getStringAsync и DoSomethingIndependent напорются на конкурентность, где её не было. Проблемы нет, если SomethingIndependent действительно independent.


"Получите обычный stack overflow exception в рантайме."

Прекрасно. А теперь внутри getStringAsync вызывается await функции, вызывающей кого-то ещё и т.д. Как диагностировать, что вызвало stack overflow exception?

Date: 2013-05-09 12:33 pm (UTC)
From: [identity profile] soonts.livejournal.com
>В кучу должны складывать все - и вызыватор, и вызываемый.
Нет, вызываемый тупо возвращает экземпляр Task. Он может быть даже выполнен синхронно уже. Таск вовсе не обязан быть реализован через async метод, он может работать как угодно ещё.
Сам async метод - да, складывает в кучу конечно, но это автомагически делает для вас компилятор.

>создаётся, но и запускается. это fork
Нет никаких форков. Все Winapi давным-давно асинхронные, в WinRT вон вообще убрали API блокирующий IO, шоб неповадно было.

>Вызыватор должен быть в курсе, где там внутри методов есть async
Тот кто зовёт вообще не в курсе как именно реализовано API. Для него это самый обычный метод, возвращающий объект Task.

>иначе getStringAsync и DoSomethingIndependent напорются на конкурентность, где её не было
Ещё раз повторяю. Фреймворк by design гарантирует исполнение вашего async-метода не более чем одним потоком одновременно (если вы конечно его только один раз позвали).

>Как диагностировать, что вызвало stack overflow exception?
Точно так же как до появления асинков, посмотреть в дебаггере на call stack.

Date: 2013-05-09 06:57 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Если всё последовательно, в каком порядке выполнятся getStringAsync и DoSomethingIndependent? getStringAsync, я так понимаю, выполняется до точки, где ему придётся заблокироваться. Далее возвращается в точку, где вызывается DoSomethingIndependent. DoSomethingIndependent чё-то сделал, и теперь мы "довыполняем" getStringAsync - ждём данные, потом что-то ещё делаем с этими данными на выходе из getStringAsync. Так? Вот в этой последовательности и есть проблема. Если getStringAsync на входе записал в файл три байта, потом DoSomethingIndependent на самом деле файл вытер, а getStringAsync хочет на выходе ещё что-то в файл написать - асинхронный ад. Это не возможный вариант развития событий?


И вон в параллельной ветке упоминают pool, который async таски подбирает.


Про fork - это термин.

(no subject)

From: [identity profile] soonts.livejournal.com - Date: 2013-05-09 09:33 pm (UTC) - Expand

(no subject)

From: [identity profile] sassa-nf.livejournal.com - Date: 2013-05-09 09:54 pm (UTC) - Expand

(no subject)

From: [identity profile] soonts.livejournal.com - Date: 2013-05-09 10:11 pm (UTC) - Expand

(no subject)

From: [identity profile] sassa-nf.livejournal.com - Date: 2013-05-10 07:24 am (UTC) - Expand

(no subject)

From: [identity profile] soonts.livejournal.com - Date: 2013-05-10 02:30 pm (UTC) - Expand

Date: 2013-05-08 08:33 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
я не понял, async / await делает что? Т.е. в АПИ видно, как это задумано, но мне интересно - запускается на каждый async новый поток? Или fork/join есть? Кто и как решает, достойно ли вычисление выполнять async (даже если оно декларировано async)? И чем это отличается от lazy val в Скале или натурально ленивого вычисления в Х-е для сравнения?

Ну ок, от lazy val в Скале отличается, видимо, тем, что апликативный порядок, а вот за Х-ь интересно.

Date: 2013-05-08 06:08 pm (UTC)
From: [identity profile] soonts.livejournal.com
>делает что?
Простите шо вынужден послать вас в google но это как раз тот случай когда это необходимо.
Дофига всего делает.

>запускается на каждый async новый поток?
Зависит главным образом от того, кто реализовывал метод, который ждёте.
Или если запускаете новый таск путём вызова Task.Run, от того, указали ли флажок TaskCreationOptions.LongRunning (да и то это просто hint для scheduler'а).
В большинстве случаев ответ "нет, никаких новых потоков не запускается, при необходимости используются потоки, имеющиеся в thread pool".

>fork/join есть?
Не понял вопрос.
Есть Task.Run(), Task.WhenAny(), Task.WhenAll(), Task.ContinueWith() и множество других API разной степени полезности.

>Кто и как решает, достойно ли вычисление выполнять async (даже если оно декларировано async)?
Код метода до первого await - вы решаете. Остальной код метода - решает в основном специальный scheduler, но в 95% случаев он работает как надо без усилий с вашей стороны, в оставшихся 5% требуются тривиальные действия шоб это указать.

>И чем это отличается от
async-await имеет совсем мало общего с ленивыми вычислениями. Я не знаю X, но почти уверен что ответ "всем отличается".
Edited Date: 2013-05-08 06:13 pm (UTC)

Date: 2013-05-08 06:27 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Однако, дело не в гугле, где я могу лишь поверхностно ознакомиться, а в том, что вы считаете таким новаторским.

То, как вы описываете эту фичу, похоже на джаву, где тоже есть fork / join со всякими примочками. Но это не то. Здесь программисту нужно вручную размечать, какие кусочки следует выполнять параллельно.

В Х-е есть разделение задачи на кусочки и параллельное их вычисление, с той огромной разницей, что "оно само". я не настолько крут, чтобы подробно объяснить, как оно именно в Х-е сделано, но как аргумент в пользу языковых фич сгодится. В Пласмейере обсуждается технология - как там strictness analysis и лень позволяют параллелизовать программу как она есть.

Date: 2013-05-08 06:41 pm (UTC)
From: [identity profile] soonts.livejournal.com
>что вы считаете таким новаторским.
Стало в разы проще писать и отлаживать эффективный асинхронный параллельный I/O-bound код.
"Эффективный" = минимальный CPU usage при заданном throughput (особенно актуально на смартфонах где CPU usage=батарейка), или максимальный throughput (особенно актуально на серверах).

Для параллельного CPU-bound кода async-await не нужен, нужен TPL.
Но я его не использовал поскольку не было нужды.
И насколько я понимаю, аналоги разной степеени годности нынче есть во всех языках, начиная с C++.

async/await делает что?

Date: 2013-05-09 09:53 am (UTC)
From: [identity profile] bik-top.livejournal.com
В параллельном и конкурентном программировании разбираюсь не очень, но тем не менее внесу свои пять копеек.

В .NET пару версий назад появился TPL (Task Parallel Library) — раздел стандартной библиотеки, крутящийся вокруг класса Task<TResult>. Этот класс представляет из себя то, что в теории называется «фьючерка» (калька с английского ввиду незнания русского аналога). Создание таких задач абстрагирует от манимуляций с конкретными потоками. При простейшей форме использования для задач берутся потоки из пула, но такие детали при необходимсти можно настраивать.

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

Что происходит в примерах, приводимых выше soonts? Асинхронность заключается в том, чтобы:
1) указать, что надо сделать параллельно,
2) указать, что доделывать после того, как параллельная часть будет выполнена,
3) вернуть управление.

То есть тот самый CPS на замыканиях, изменений языка для этого делать нет необходимости, можно обойтись библиотечными средствами старого C#. Теперь к твоему вопросу.

> я не понял, async/await делает что?

async не делает ровным счётом ничего; тут важно, что делает await. Он позволяет автоматизировать составление замыкания, используемого в CPS. При этом производит проброску исключений из фьючерки в вызывающий поток, предоставляет механизм кооперативной отмены (а не дикое киляние потока), по умолчанию сохраняет контекст синхронизации (если фьючерка была порождена в UI-потоке, то из continuation'а после task'а можно менять UI без хитрой диспетчеризации), etc. Плюс await имеет очень удобный синтаксис; конечно, можно переписать функцию на Task'ах, через которое всё и работает, фактически без изменения вызывающей стороны — как это выглядело бы в аналогичных API других языков и платформ. Но, скажем, если дело происходит в цикле, то надо в continuation протаскивать состояние обхода цикла, в цепочку continuation'ов передавать необходимость выполнить следующую итерацию, и прочий геморрой с исключениями. C# 5.0 же позволяет делать это автоматически, генерируя нужный boilerplate code из синтаксиса, который внешне похож на обычный синхронный, и отлаживается тоже просто.

Re: async/await делает что?

Date: 2013-05-09 11:09 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
Спасибо, я его как фьючерки и воспринял.

CPS автоматом - тоже интересно, но возникают вопросы, как там с интеропом с кодом с другими call conventions - смесь async и не async, как используется нативный стек и как preserve program order, если внезапно наследники сделают куски кода async. Мне кажется, это концептуальная проблема, потому очень интересно, как этот вопрос решается или ограничивается круг проблем.

Re: async/await делает что?

Date: 2013-05-09 11:34 am (UTC)
From: [identity profile] bik-top.livejournal.com
> CPS автоматом - тоже интересно, но возникают вопросы, как там с интеропом с кодом с другими call conventions - смесь async и не async

На все вопросы не отвечу, просто в общих чертах. С точки зрения пользователя некоего асинхронного API: он видит методы, которые возвращают Task или Task<TResult>, вполне себе традиционно. Если автор API решит внутри реимплементировать эти методы через новые языковые фичи и новые асинхронные методы стандартной библиотеки, то внутри у него где-то появится await, и компилятор обяжет его пометить метод словом async. Но! Частью сигнатуры метода это слово не является, то есть для пользователя метода осталось всё по-прежнему (не произошло ломающего изменения), он может использовать возвращаемый Task по старинке, или await'ить его по-модному. Т.е. async в объявлении метода, возвращающего таск, — это указание компилятору, как компилировать содержимое метода (упрощённо говоря, часть «плоского кода» после await вкомпилируется в замыкание-continuation), но не навязывает требования изменения способа вызова. Собственно, этот флажок async в сигнатуре метода не принципиален, компилятор теоретически сам может внутри определения метода увидеть await и сгенерировать соответствующий код. Просто await решено было сделать контекстным ключевым словом (в том числе для обратной совместимости с кодом, который использовал await просто как обычный идентификатор), и чтоб отличать его вхождение в код метода как инструкцию, добавили требование пометить этот метод словом async. Т.е. это слово учитывается компилятором при генерации кода метода, но не учитывается при генерации кода использования метода.

Re: async/await делает что?

Date: 2013-05-09 11:52 am (UTC)
From: [identity profile] soonts.livejournal.com
Мне кажется я только что чуть ниже ответил на этот ваш камент, не?

Re: async/await делает что?

Date: 2013-05-09 12:16 pm (UTC)
From: [identity profile] soonts.livejournal.com
>как там с интеропом с кодом с другими call conventions
Как обычно у Microsoft, всё прекрасно с интеропом.

Завернуть "старый" async API (который BeginXxx/EndXxx) в новый с тасками — одна строчка кода.
Блокировать тред дожидаясь таска — тоже одна, правда если есть контекст синхронизации, возможен deadlock. Да и вообще треды не нужно блокировать, они созданы быть свободными.
Синхронизировать два таска, не завершая ни один из них, чуть больше одной строчки но меньше 10 (можно например создать экземпляр класса TaskCompletionSource, потом в одном TaskCompletionSource.SetResult, во втором await TaskCompletionSource.Task).

Re: async/await делает что?

Date: 2013-05-09 06:59 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Я не сомневаюсь, что работает. Фича хорошая. Я пытаюсь разобраться, какие вопросы они решили и как.

Re: async/await делает что?

Date: 2013-05-09 11:51 am (UTC)
From: [identity profile] soonts.livejournal.com
Всё верно кроме вот этого:

>автоматизировать составление замыкания, используемого в CPS
async-await вообще не использует замыканий, замыкания не позволяют например прыгнуть в середину цикла.
Вместо замыканий генерируется internal class, который хранит стейт (включающий помимо прочего аргументы async метода, и локальные переменные в нём).
И ещё он реализует интерфейс IAsyncStateMachine.
Вот как выглядит главный метод MoveNext этого internal класса для моего примера выше про 100 байт и XOR, после небольшого рефакторинга для читаемости:

void MoveNext()
{
    try
    {
        switch( this._state ) // Jump to the middle of the method based on the state.
        {
            case 0:
                goto Label_0067;
            case 1:
                goto Label_00F4;
        }

        // The state was -1 => run from the start.
        this.res = 0;
        this.i = 0;

        while( this.i < 100 )
        {
            TaskAwaiter<byte> local1 = this.socket.ReadByte().GetAwaiter();
            if( local1.IsCompleted )
                goto Label_0085; // If the await completed synchronously, the task continues to run on the same thread.

            this._state = 0;
            this.awaiter4 = local1;
            this._builder.AwaitUnsafeOnCompleted<TaskAwaiter<byte>, Program.DoThatInternalClass>( ref local1, ref this );
            return;
        
        Label_0067: // Came here because the first await has completed.
            local1 = this.awaiter4;
            this.awaiter4 = new TaskAwaiter<byte>();
            this._state = -1;

        Label_0085: // This label is not an entry point.
            local1 = new TaskAwaiter<byte>();
            this.b = local1.GetResult();
            this.res = (byte)( this.res ^ this.b );
            TaskAwaiter local4 = this.socket.WriteByte( this.res ).GetAwaiter();
            if( local4.IsCompleted )
                goto Label_0113; // If the await completed synchronously, the task continues to run on the same thread.

            this._state = 1;
            this.awaiter5 = local4;
            this._builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.DoThatInternalClass>( ref local4, ref this );
            return;

        Label_00F4: // Came to this method because the 2-nd await has completed.
            local4 = this.awaiter5;
            this.awaiter5 = new TaskAwaiter();
            this._state = -1;

        Label_0113: // This label is not an entry point.
            local4.GetResult();
            local4 = new TaskAwaiter();
            this.i++;
        }
    }
    catch( Exception ex )
    {
        this._state = -2;
        // Marshall the exception to the caller.
        this._builder.SetException( ex );
        return;
    }
    this._state = -2;
    // Awake the caller because the method was completed successfully.
    // If the synchronization context permits, this will happen on this same thread, right from the inside of SetResult() method.
    this._builder.SetResult(); 
}

Re: async/await делает что?

Date: 2013-05-09 11:54 am (UTC)
From: [identity profile] bik-top.livejournal.com
> Вместо замыканий генерируется internal class, который хранит стейт

Это я и назвал замыканием. Замыкание-в-широком-смысле, если угодно. Захват контекста.

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

Да, я как раз про цикл упомянул выше как пример, когда простой код будет сложно переписать на продолжениях.
Edited Date: 2013-05-09 12:06 pm (UTC)

await

Date: 2013-05-09 09:22 am (UTC)
From: [identity profile] bik-top.livejournal.com
> В том и элегантность, что огромную кучу сложной, но необходимой логики спрятали за двумя несложными для понимания ключевыми словами.

Или даже за одним словом — await. async декоративный фактически.

Profile

thedeemon: (Default)
Dmitry Popov

February 2026

S M T W T F S
12 34567
891011121314
15161718192021
22232425262728

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Mar. 16th, 2026 01:45 pm
Powered by Dreamwidth Studios