惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cyber Attacks, Cyber Crime and Cyber Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 三生石上(FineUI控件)
博客园 - Franky
博客园_首页
Jina AI
Jina AI
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
雷峰网
雷峰网
云风的 BLOG
云风的 BLOG
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
V
V2EX
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
Attack and Defense Labs
Attack and Defense Labs
博客园 - 聂微东
G
Google Developers Blog
V
Visual Studio Blog
Security Latest
Security Latest
T
Tenable Blog
爱范儿
爱范儿
Know Your Adversary
Know Your Adversary
H
Heimdal Security Blog
博客园 - 司徒正美
K
Kaspersky official blog
The Last Watchdog
The Last Watchdog
酷 壳 – CoolShell
酷 壳 – CoolShell
PCI Perspectives
PCI Perspectives
T
The Exploit Database - CXSecurity.com
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
MongoDB | Blog
MongoDB | Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Martin Fowler
Martin Fowler
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Secure Thoughts
Cyberwarzone
Cyberwarzone
量子位
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Project Zero
Project Zero
Blog — PlanetScale
Blog — PlanetScale
L
LINUX DO - 最新话题
IT之家
IT之家

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Async/Await в C# это синтаксический сахар для конечного автомата
Дарья Карман · 2026-05-15 · via Все публикации подряд на Хабре

Простой

8 мин

10K

Конечный автомат и его внутреннее устройство

Примечание переводчика:

  • State Machine, конечный автомат это преобразованный async метод. Компилятор преобразует метод в тип, реализующий конечный автомат (наследуется от IAsyncStateMachine). Благодаря такому механизму, при достижении первого оператора await поток, начавший метод, может возвращаться без «физического» оператора return метода, тем самым, продолжая выполнение основной программы.

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

    (Возможные) состояния конечного автомата:

    • -1 — Начальное состояние (Initial State): Это состояние до начала выполнения метода. Когда выполнение только начинается, автомат находится именно в этой точке.

    • 0, 1, 2... — Промежуточные состояния (Intermediate States): Каждому ключевому слову await в вашем методе присваивается уникальное числовое состояние (начиная с 0). Когда выполнение доходит до ожидания и метод приостанавливается, автомат запоминает это число. Как только ожидаемая операция завершается, он «просыпается» и, глядя на это число, точно знает, в каком месте кода нужно продолжить выполнение и какие локальные переменные восстановить .

    • -2 — Конечное состояние (Final State): Это состояние сигнализирует о том, что метод полностью завершил свою работу. Неважно, успешно ли он выполнился или выбросил исключение — после того, как работа закончена, состояние устанавливается в -2 .

  • Перед выполнением асинхронной операции компилятор, встречающий оператор await, берет указанный операнд и пытается вызвать для него метод GetAwaiter. Awaiter это объект ожидания, который мы получаем от GetAwaiter. Объект ожидания будет ждать завершения асинхронной операции и возвращать ее результат.

    Аналогия: пицца — асинхронная операция. Оператор await — ожидать пиццу. Объект ожидания awaiter — доставщик пиццы.

  • Построитель (например, AsyncTaskMethodBuilder<T>) — это внутренний механизм компилятора, который конструирует и управляет жизненным циклом объекта Task для асинхронного метода

Простыми словами, async/await это своего рода синтаксический сахар. Каждый асинхронный метод будет преобразован в StateMachine, и затем вызывающий метод использует ее для выполнения бизнес‑логики.

Некоторым нравится сначала теория, некоторые хотят сразу увидеть код. Я планирую использовать гибридный подход: сначала небольшая доза теории, затем весь код для конечного автомата (с полезными комментариями), а потом попробуем нарисовать схему, чтобы объяснить алгоритм выполнения кода внутри конечного автомата.

Несколько терминов, которые будут использоваться в статье:

  • WorkerFunction: метод, который будет выполнять фактическую асинхронную работу.

  • CallingFunction: метод, который будет вызывать WorkerFunction.

  • FirstCall: первый вызов метода MoveNext у конечного автомата (синхронный поток выполнения).

  • WakeUpCall: момент, когда результаты операции await становятся доступными, и код продолжает выполнение с того места, где он остановился. Своего рода callback (обратный вызов).

Что происходит при компиляции кода (кратко — теория)

Мы берем наш фрагмент кода и вставляем его на sharplab, а затем он генерирует скомпилированный код для этого фрагмента. Вот несколько вещей, которые компилятор сгенерирует для нашего асинхронного кода:

  1. Компилятор сгенерирует код конечного автомата (реализующийIAsyncStateMachine) для WorkerFunction.

  2. Перенесет фактическую логику WorkerFunction в функцию MoveNext.

  3. Создаст внутри конечного автомата переменные, необходимые для его работы.

  4. Изменит CallingFunction так, чтобы он создавал новый экземпляр StateMachine.

  5. Вызовет Start у одного из генераторов задач (TaskMethodGenerator) конечного автомата внутри CallingFunction (подробнее ниже).

Теперь посмотрим на код

Возьмем очень простой фрагмент кода, в котором используются ключевые слова async/await. Я намеренно сохраняю сложность кода минимальной, так как нам нужно понять работу async/await, а не возможные варианты применения асинхронности. Это заслуживает отдельной статьи.

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;

namespace Scenario
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AsyncDownload().GetAwaiter().GetResult();
                Console.ReadLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        static async Task<string> AsyncDownload()
        {
            HttpClient client = new HttpClient();
            //Асинхронно скачиваем контент веб-страницы
            return await client.GetStringAsync("https://msdn.microsoft.com");
        }

    }
}

Я вставил этот код на sharplab и скомпилировал его в режиме Debug. Полученный сгенерированный результат представлен ниже:

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace Scenario
{
    internal class Program
    {
        // Создан конечный автомат для представления асинхронного метода загрузки 
        // с использованием HTTP-клиента
        [CompilerGenerated]
        private sealed class <AsyncDownload>d__1 : IAsyncStateMachine
        {
            // Переменная для поддержания текущего состояния выполнения конечного автомата
            // FirstCall: Начальное значение -1 
            public int <>1__state;

            // Построитель для создания новой асинхронной задачи, которая будет выполнять этот код конечного автомата
            public AsyncTaskMethodBuilder<string> <>t__builder;

            // HTTP-клиент, используемый методом для загрузки содержимого удаленного URL-адреса
            private HttpClient <client>5__1;

            // Переменная для хранения результатов вызова HttpClient
            private string <>s__2;

            // Awaiter задачи по загрузке содержимого с помощью HTTP-клиента
            private TaskAwaiter<string> <>u__1;

            // Это раздел, где находится фактическая логика исходного метода
            // Содержит логику выполнения кода до оператора await
            // Также настраивает параметры вызова функции пробуждения 
            // После завершения выполнения асинхронного метода
            private void MoveNext()
            {
                // Копирует текущее состояние в локальную переменную
                // Начальное значение состояния будет -1
                int num = <>1__state;
                // Переменная для сохранения результата HTTP-запроса
                string result;
                try
                {
                    // Переменная для сохранения объекта ожидания для новой задачи
                    TaskAwaiter<string> awaiter;
                    // В первый раз num будет -1
                    if (num != 0)
                    {
                        //FirstCall: мы окажемся здесь по первичному вызову
                        <client>5__1 = new HttpClient();
                        //FirstCall: Сохранит объект ожидания для HTTP-вызова в переменную
                        awaiter = <client>5__1.GetStringAsync("https://msdn.microsoft.com").GetAwaiter();
                        //FirstCall: Скорее всего, мы перейдем к этому блоку при первом вызове
                        //FirstCall: Этот блок предназначен для оптимизации в случае, если задача уже выполнена               
                        //FirstCall: Мы пропускаем планирование вызова пробуждения или продолжения
                        if (!awaiter.IsCompleted)
                        {
                            //FirstCall: Мы устанавливаем переменную состояния в 0
                            //FirstCall: Чтобы при вызове функции пробуждения или обратном вызове мы вообще не входили в этот блок.
                            num = (<>1__state = 0);
                            <>u__1 = awaiter;
                            <AsyncDownload>d__1 stateMachine = this;
                            //FirstCall: Именно здесь происходит большая часть магии при вызове AwaitUnsafeOnCompleted
                            //FirstCall: На этом шаге мы регистрируем конечный автомат как продолжение задачи, вызывая AwaitUnsafeOnCompleted
                            //Но как это делается?
                            //builder.AwaitUnsafeOnCompleted выполняет несколько действий в фоновом режиме
                            //FirstCall: 1. TaskMethodBuilder захватывает контекст выполнения
                            //FirstCall: 2. Создает MoveNextAction, используя контекст выполнения
                            //FirstCall: 3. Этот MoveNextAction вызовет MoveNext конечного автомата и предоставит контекст выполнения
                            //FirstCall: 4. Устанавливает MoveNextAction в качестве обратного вызова для объекта ожидания при завершении, используя awaiter.UnsafeOnCompleted(action)
                            <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
                            //FirstCall: Освободить процессор или поток для вызывающей стороны
                            //FirstCall: Начинается работа "ожидания"
                            return;
                        }
                    }
                    else
                    {
                        //WakeupCall: Мы получаем объект ожидания из контекста выполнения
                        awaiter = <>u__1;
                        //WakeupCall: Установить значение объекта ожидания в null для освобождения памяти
                        <>u__1 = default(TaskAwaiter<string>);
                        //WakeupCall: Временно установить переменную состояния в значение -1 (начальное состояние)
                        num = (<>1__state = -1);
                    }

                    //WakeupCall: Получить результат от объекта ожидания
                    //WakeupCall: Объект ожидания должен завершиться, как только мы получим WakeUpCall
                    <>s__2 = awaiter.GetResult();
                    result = <>s__2;
                }
                catch (Exception exception)
                {
                    <>1__state = -2;
                    <>t__builder.SetException(exception);
                    return;
                }
                //WakeUpCall: Установить состояние на конечное, на этом всё
                <>1__state = -2;
                //WakeUpCall: Построитель завершает задачу и устанавливает её результат
                <>t__builder.SetResult(result);
            }

            void IAsyncStateMachine.MoveNext()
            {
                //ILSpy сгенерировал эту явную реализацию интерфейса из директивы .override в MoveNext
                this.MoveNext();
            }

            [DebuggerHidden]
            private void SetStateMachine(IAsyncStateMachine stateMachine)
            {
            }

            void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
            {
                //ILSpy сгенерировал эту явную реализацию интерфейса из директивы .override в SetStateMachine
                this.SetStateMachine(stateMachine);
            }
        }
        
        // Далее, мы будем называть функцию Main как "CallingFunction"
        private static void Main(string[] args)
        {
            try
            {
                AsyncDownload().GetAwaiter().GetResult();
                Console.ReadLine();
            }
            catch (Exception value)
            {
                Console.WriteLine(value);
                throw;
            }
        }

        // Мы будем называть этот метод "WorkerMethod"
        [AsyncStateMachine(typeof(<AsyncDownload>d__1))]
        [DebuggerStepThrough]
        private static Task<string> AsyncDownload()
        {
            //FirstCall: Создать новый экземпляр конечного автомата
            <AsyncDownload>d__1 stateMachine = new <AsyncDownload>d__1();
            //FirstCall: Создать новый экземпляр AsyncTaskMethodBuilder и настроить его для конечного автомата
            stateMachine.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
            //FirstCall: Установить состояние конечного автомата в -1 (начальное)
            stateMachine.<>1__state = -1;
            AsyncTaskMethodBuilder<string> <>t__builder = stateMachine.<>t__builder;
            //FirstCall: Вызовет метод Start в построителе, который приведет к вызову StateMachine.MoveNext();
            <>t__builder.Start(ref stateMachine);
            //FirstCall: Возвращает задачу из построителя от WorkerMethod
            return stateMachine.<>t__builder.Task;
        }
    }
}

Пояснение к приведенному выше коду

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

Я использую цветовые паттерны для лучшего понимания.

  • Все блоки с красной рамкой будут выполнены как при первом вызове FirstCall, так и при вызове пробуждения WakeUpCall.

  • Синие блоки будут выполнены только при FirstCall.

  • Зеленые блоки могут быть выполнены при FirstCall, если объект ожидания уже завершил операцию, но это крайне маловероятно, поскольку в этом процессе нет оптимизаций.

  • Зеленые блоки будут выполнены при вызове WakeUpCall в случае отсутствия ошибок или исключений.

Алгоритм работы конечного автомата во время выполнения метода

Алгоритм работы конечного автомата во время выполнения метода


Также по этой теме рекомендую к прочтению статью «Другой способ понять, как работает async/await в C#»