День 2810. #ЧтоНовенького #VSCode
Более Быстрый и Лёгкий C# Dev Kit
Мы, разработчики, любим быстрые инструменты. Ожидание загрузки языковой поддержки, завершения сборки и выполнения тестов — это время, которое можно было бы потратить с большей пользой. В рамках выпуска .NET 11 архитектура C# Dev Kit была полностью переработана, что позволило существенно повысить производительность и оптимизировать использование памяти. Кроме того, появились отличные новые функции. Версия C# Dev Kit синхронизирована с текущим выпуском .NET, поэтому новая версия расширения — 11.0, хотя предыдущая была 3.3.
Более быстрая загрузка
В 100+ раз быстрее для активного файла и до 30 раз быстрее для всего решения.
Загрузка активного файла означает, что для редактируемого файла доступна языковая поддержка: автодополнение, диагностика, переход к определению и быстрые исправления. В этот момент можно приступать к полноценной работе. Загрузка всего решения достигается после завершения загрузки всех проектов: языковая поддержка работает в любом файле, обнаружены все тесты в решении, а функция поиска всех ссылок (Find All References) охватывает все проекты. Ускорить загрузку помогают новые механизмы кэширования проектов. Теперь системе не нужно заново анализировать каждый проект при каждом открытии, поэтому языковые сервисы, тесты и таргеты запуска становятся доступными практически мгновенно. Сначала загружается активный файл, что позволяет начать работу с ним, пока остальная часть рабочей области загружается в фоновом режиме; при этом производительность остаётся стабильной независимо от количества проектов.
Кэш проекта создается при его первой загрузке. Все последующие загрузки выполняются на основе кэша и происходят быстро. Если зафиксировать файлы кэша в системе контроля версий, то новые клоны репозитория и рабочие деревья (git worktrees) сразу же получат доступ к быстрой работе инструментов, что особенно удобно при использовании агентов сборки.
Более быстрая сборка
В C# Dev Kit 11.0 инкрементальная сборка выполняется значительно быстрее благодаря функциям быстрой проверки актуальности (fast up-to-date checks) и ускорению сборки (Build Acceleration) — возможностям, которые уже давно доступны разработчикам в Visual Studio.
Время сборки зависит от структуры решения, поэтому столь значительное ускорение будет наблюдаться не в каждом случае. Однако чаще всего выполняется сборка после внесения лишь небольших изменений, и именно в этом сценарии версия 11.0 демонстрирует наибольший прирост производительности.
Повышенная отзывчивость заметна также при запуске модульных тестов и самих приложений. Поскольку перед началом выполнения обе эти операции требуют сборки, в версии 11.0 они завершаются быстрее.
Память
Потребление памяти ниже более, чем на 80%.
Объём используемой памяти зависит скорее от размера кодовой базы, чем от количества проектов. При этом благодаря компактной модели представления проектов потребление памяти растёт гораздо медленнее, чем размер кода.
В версии 11.0 используется более компактное представление проекта в оперативной памяти, что существенно снижает потребление памяти, особенно при работе с крупными решениями.
Редактирование проектов и других файлов MSBuild
В C# Dev Kit 11.0 реализованы новые возможности редактирования файлов
Для пакетов также предусмотрена функция CodeLens, предлагающая ряд удобных действий. Она уведомляет о появлении новых версий и о наличии уязвимостей в используемой версии.
Языковая поддержка MSBuild не ограничивается только пакетами. Функции автодополнения охватывают свойства, элементы и ключи метаданных. Переход к определению работает для ряда элементов и позволяет просматривать исходный код SDK. Система диагностики выявляет проблемы в процессе ввода кода, а функция быстрых исправлений предлагает способы их устранения.
Проверка состояния с помощью C# Doctor
Если проект не загружается или ведёт себя неожиданным образом, причина часто кроется в окружении: отсутствует необходимый SDK, не установлена среда выполнения, недоступна целевая платформа, произошёл сбой при восстановлении пакетов или в одном из подключенных пакетов обнаружена известная уязвимость. C# Doctor объединяет все эти проверки в одном месте и помогает найти верное решение, избавляя вас от необходимости восстанавливать картину произошедшего по разрозненным сообщениям об ошибках.
Источник: https://devblogs.microsoft.com/dotnet/faster-lighter-csharp-dev-kit/
Более Быстрый и Лёгкий C# Dev Kit
Мы, разработчики, любим быстрые инструменты. Ожидание загрузки языковой поддержки, завершения сборки и выполнения тестов — это время, которое можно было бы потратить с большей пользой. В рамках выпуска .NET 11 архитектура C# Dev Kit была полностью переработана, что позволило существенно повысить производительность и оптимизировать использование памяти. Кроме того, появились отличные новые функции. Версия C# Dev Kit синхронизирована с текущим выпуском .NET, поэтому новая версия расширения — 11.0, хотя предыдущая была 3.3.
Более быстрая загрузка
В 100+ раз быстрее для активного файла и до 30 раз быстрее для всего решения.
Загрузка активного файла означает, что для редактируемого файла доступна языковая поддержка: автодополнение, диагностика, переход к определению и быстрые исправления. В этот момент можно приступать к полноценной работе. Загрузка всего решения достигается после завершения загрузки всех проектов: языковая поддержка работает в любом файле, обнаружены все тесты в решении, а функция поиска всех ссылок (Find All References) охватывает все проекты. Ускорить загрузку помогают новые механизмы кэширования проектов. Теперь системе не нужно заново анализировать каждый проект при каждом открытии, поэтому языковые сервисы, тесты и таргеты запуска становятся доступными практически мгновенно. Сначала загружается активный файл, что позволяет начать работу с ним, пока остальная часть рабочей области загружается в фоновом режиме; при этом производительность остаётся стабильной независимо от количества проектов.
Кэш проекта создается при его первой загрузке. Все последующие загрузки выполняются на основе кэша и происходят быстро. Если зафиксировать файлы кэша в системе контроля версий, то новые клоны репозитория и рабочие деревья (git worktrees) сразу же получат доступ к быстрой работе инструментов, что особенно удобно при использовании агентов сборки.
Более быстрая сборка
В C# Dev Kit 11.0 инкрементальная сборка выполняется значительно быстрее благодаря функциям быстрой проверки актуальности (fast up-to-date checks) и ускорению сборки (Build Acceleration) — возможностям, которые уже давно доступны разработчикам в Visual Studio.
Время сборки зависит от структуры решения, поэтому столь значительное ускорение будет наблюдаться не в каждом случае. Однако чаще всего выполняется сборка после внесения лишь небольших изменений, и именно в этом сценарии версия 11.0 демонстрирует наибольший прирост производительности.
Повышенная отзывчивость заметна также при запуске модульных тестов и самих приложений. Поскольку перед началом выполнения обе эти операции требуют сборки, в версии 11.0 они завершаются быстрее.
Память
Потребление памяти ниже более, чем на 80%.
Объём используемой памяти зависит скорее от размера кодовой базы, чем от количества проектов. При этом благодаря компактной модели представления проектов потребление памяти растёт гораздо медленнее, чем размер кода.
В версии 11.0 используется более компактное представление проекта в оперативной памяти, что существенно снижает потребление памяти, особенно при работе с крупными решениями.
Редактирование проектов и других файлов MSBuild
В C# Dev Kit 11.0 реализованы новые возможности редактирования файлов
.csproj, .props и .targets. Вам доступны автодополнение кода, диагностика, переход к определению, быстрые исправления и семантическая подсветка — тот же уровень языковой поддержки, к которому вы привыкли при редактировании кода на C#.Для пакетов также предусмотрена функция CodeLens, предлагающая ряд удобных действий. Она уведомляет о появлении новых версий и о наличии уязвимостей в используемой версии.
Языковая поддержка MSBuild не ограничивается только пакетами. Функции автодополнения охватывают свойства, элементы и ключи метаданных. Переход к определению работает для ряда элементов и позволяет просматривать исходный код SDK. Система диагностики выявляет проблемы в процессе ввода кода, а функция быстрых исправлений предлагает способы их устранения.
Проверка состояния с помощью C# Doctor
Если проект не загружается или ведёт себя неожиданным образом, причина часто кроется в окружении: отсутствует необходимый SDK, не установлена среда выполнения, недоступна целевая платформа, произошёл сбой при восстановлении пакетов или в одном из подключенных пакетов обнаружена известная уязвимость. C# Doctor объединяет все эти проверки в одном месте и помогает найти верное решение, избавляя вас от необходимости восстанавливать картину произошедшего по разрозненным сообщениям об ошибках.
Источник: https://devblogs.microsoft.com/dotnet/faster-lighter-csharp-dev-kit/
👍11👎1
День 2811. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
49. Модульное тестирование
«Как бы вы использовали модульные тесты в приложении .NET? Опишите процесс настройки фреймворка тестирования, написания базового теста и его запуска. Укажите также, каких лучших практик вы придерживаетесь при написании модульных тестов».
Хороший ответ
Начнём с добавления проекта тестов в решение. Например, для тестового фреймворка xUnit это можно сделать с помощью .NET CLI:
Нужно убедиться, что тестовый проект содержит ссылку на тестируемый проект.
При структурировании тестов полезен шаблон AAA (Arrange, Act, Assert). Он предполагает подготовку окружения (Arrange), выполнение тестируемой функциональности (Act) и проверку результата (Assert). Если мы тестируем метод, складывающий два числа, тест может выглядеть так:
Запустить тесты можно с помощью .NET CLI:
либо в IDE через «Обозреватель тестов» (Test Explorer).
Основные принципы написания тестов
- Изоляция: обеспечение независимости каждого теста; он не должен зависеть от других тестов.
- Соглашения об именовании: использование понятных имён для тестовых методов, описывающих ожидаемое поведение теста.
- Моки: использование фреймворков для создания заглушек (например, Moq), чтобы изолировать тесты от внешних зависимостей и обеспечить их детерминированность.
- Покрытие тестами: стремление к высокому уровню покрытия, но с упором на проверку критически важных путей и логики, а не простых методов доступа (геттеров/сеттеров).
Распространённый неудачный ответ
«Чаще всего я просто копирую существующие тесты из другого проекта. Обычно они похожи, а xUnit не требует какой-то особой конфигурации».
Почему этот подход неверен:
- Непонимание конкретных требований: у каждого проекта могут быть уникальные требования и конфигурации, которые такой подход не учитывает. Копирование без понимания контекста может привести к созданию бесполезных тестов или пропуску граничных случаев.
- Низкое качество проектирования тестов: такой подход может переносить плохие практики или неактуальные тесты из одного проекта в другой, снижая эффективность набора тестов.
- Игнорирование специфики тестов: тесты должны разрабатываться с учётом конкретной логики и сценариев данного проекта, а не на основе универсальных случаев, которые могут быть неприменимы.
Подобный неверный ответ часто свидетельствует о непонимании принципов тестирования или пренебрежении процессами обеспечения качества, указывая на поверхностный подход к тестированию ПО.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
49. Модульное тестирование
«Как бы вы использовали модульные тесты в приложении .NET? Опишите процесс настройки фреймворка тестирования, написания базового теста и его запуска. Укажите также, каких лучших практик вы придерживаетесь при написании модульных тестов».
Хороший ответ
Начнём с добавления проекта тестов в решение. Например, для тестового фреймворка xUnit это можно сделать с помощью .NET CLI:
dotnet new xunit -n YourProjectName.TestsНужно убедиться, что тестовый проект содержит ссылку на тестируемый проект.
При структурировании тестов полезен шаблон AAA (Arrange, Act, Assert). Он предполагает подготовку окружения (Arrange), выполнение тестируемой функциональности (Act) и проверку результата (Assert). Если мы тестируем метод, складывающий два числа, тест может выглядеть так:
using Xunit;
public class CalculatorTests
{
[Fact]
public void Add_ReturnsCorrectSum()
{
// Arrange
var calc = new Calculator();
int a = 5;
int b = 7;
// Act
var result = calc.Add(a, b);
// Assert
Assert.Equal(12, result);
}
}
Запустить тесты можно с помощью .NET CLI:
dotnet test YourProjectName.Testsлибо в IDE через «Обозреватель тестов» (Test Explorer).
Основные принципы написания тестов
- Изоляция: обеспечение независимости каждого теста; он не должен зависеть от других тестов.
- Соглашения об именовании: использование понятных имён для тестовых методов, описывающих ожидаемое поведение теста.
- Моки: использование фреймворков для создания заглушек (например, Moq), чтобы изолировать тесты от внешних зависимостей и обеспечить их детерминированность.
- Покрытие тестами: стремление к высокому уровню покрытия, но с упором на проверку критически важных путей и логики, а не простых методов доступа (геттеров/сеттеров).
Распространённый неудачный ответ
«Чаще всего я просто копирую существующие тесты из другого проекта. Обычно они похожи, а xUnit не требует какой-то особой конфигурации».
Почему этот подход неверен:
- Непонимание конкретных требований: у каждого проекта могут быть уникальные требования и конфигурации, которые такой подход не учитывает. Копирование без понимания контекста может привести к созданию бесполезных тестов или пропуску граничных случаев.
- Низкое качество проектирования тестов: такой подход может переносить плохие практики или неактуальные тесты из одного проекта в другой, снижая эффективность набора тестов.
- Игнорирование специфики тестов: тесты должны разрабатываться с учётом конкретной логики и сценариев данного проекта, а не на основе универсальных случаев, которые могут быть неприменимы.
Подобный неверный ответ часто свидетельствует о непонимании принципов тестирования или пренебрежении процессами обеспечения качества, указывая на поверхностный подход к тестированию ПО.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👎2
День 2812. #Карьера #AI
Как Разработчикам Оставаться Полезными, Когда Код Пишет ИИ?
По мере того, как ИИ берёт на себя всё большую часть задач по написанию кода, GitHub призывает разработчиков совершенствовать навыки, необходимые для управления этим процессом и принятия решений о том, какой код будет выпущен.
1. Управляйте агентами. Ставьте задачи, предоставляйте контекст и оценивайте результаты. Это процесс координации работы отдельных агентов, отвечающих за аутентификацию, документацию и тестирование, при сохранении за собой ответственности за итоговый результат.
2. Подвергайте результаты сомнению. Попросите другую модель проанализировать и раскритиковать предложенное решение, а затем самостоятельно оцените оба ответа.
3. Решайте более масштабные задачи. Используйте время, высвободившееся от непосредственного написания кода, для анализа потребностей клиентов, архитектуры и выбора оптимальных решений. Разработчикам по-прежнему необходимо определять, действительно ли предлагаемое решение устраняет нужную проблему.
Неудивительно, что этот вопрос, затрагивающий саму суть профессии, всё чаще обсуждается в отрасли, в том числе на крупных конференциях для разработчиков, которые раньше были сосредоточены исключительно на технических аспектах написания кода. На ноябрьской конференции Live!360 Tech Con докладчики обсуждали, что люди должны по-прежнему понимать, оценивать и за что нести ответственность.
Для Анджелы Дуган, вице-президента по поставке продуктов в Red Foundry, ответ кроется в софт-скилах. В своей статье "Human Skills in an AI World: How Technologists Stay Essential" она делает упор на формировании общего понимания задач, критическом пересмотре предположений и умении вовремя остановиться и оценить ситуацию.
Для Дуган это личный вопрос: после того как в июле 2025 года она попала под сокращение в Microsoft, новый работодатель высоко оценил её лидерские качества, способность принимать взвешенные решения, навыки коммуникации и умение справляться со сложными задачами. Она рекомендует использовать ИИ прежде всего для выполнения рутинных задач с низким уровнем риска, высвобождая время сотрудников для деятельности, требующей творческого подхода, выстраивания отношений и определения стратегического направления.
Марк Микейлис из компании IntelliTect подходит к проблеме с точки зрения обучения разработчиков. В статье "Learning to Program in .NET in an AI World" он утверждает, что знания в области архитектуры, структуры кода, тестирования и DevOps остаются необходимыми, даже когда разработчики делегируют написание кода ИИ.
Микейлис предупреждает, что технический долг может накапливаться стремительно, а большое количество тестов, созданных ИИ, не всегда обеспечивает надёжную защиту. Разработчикам необходимо оценивать, действительно ли тесты проверяют то поведение системы, которое от неё ожидается. Он также советует новичкам самостоятельно анализировать сгенерированный код, а затем сравнивать свои выводы с результатами анализа, выполненного ИИ, чтобы понять, что они упустили из виду.
Необходимость в критическом осмыслении распространяется и на вопрос о том, какой объём полномочий следует предоставлять системам ИИ. В статье "The Irresponsible AI Initiative" ведущие DevOps-архитекторы GitHub Микки Гуссет и Кен Мьюз описывают многие сбои в работе ИИ как ошибки проектирования и управления, которых можно было бы избежать. По их мнению, наличие у агента технической возможности получить доступ к информации или выполнить действие вовсе не означает, что ему следует разрешать это делать.
«Промпты не являются границами безопасности», — поясняют они. Их рекомендации включают:
- Обеспечение принципа минимальных привилегий с помощью механизмов контроля, действующих вне модели.
- Тестирование агентов в «песочнице» на устойчивость к вредоносным входным данным, атакам типа «инъекция промпта» (prompt injection) и неоднозначным запросам.
- Поэтапное внедрение функций выполнения реальных действий — с обязательным одобрением человеком, ведением журналов аудита, установкой лимитов на расходы и прав доступа, а также наличием функции немедленной остановки (kill switch).
Они также настаивают на назначении ответственного лица, уполномоченного принимать бизнес-риски, связанные с развёртыванием системы. Эта ответственность остаётся за людьми, даже когда агенты обретают большую автономность.
Дополнительные советы по построению карьеры в эпоху ИИ
- Используйте ИИ в качестве наставника (для новичков). GitHub рекомендует просить Copilot разъяснять концепции и подходы, не выдавая сразу готовые решения. Также предлагается временно отключать функцию автодополнения кода (inline completions) во время обучения, чтобы разработчики учились решать задачи самостоятельно, сохраняя при этом возможность получать помощь через чат.
- Совершенствуйте повседневные инженерные навыки. В статье GitHub "Why developer expertise matters more than ever in the age of AI" подчёркивается, что создание пулл-реквестов, проведение код-ревью и ведение документации служат фундаментом для эффективной работы с использованием ИИ. Практические рекомендации включают в себя ограничение области изменений, обоснование их необходимости, а также самостоятельную проверку и тестирование кода перед отправкой.
- Продолжайте учиться у других разработчиков. В статье Microsoft "AI Is Changing How We Code. It's Also Changing How We Learn" Джеймс Ремпт утверждает, что по мере совершенствования инструментов ИИ ценность непосредственного обучения у опытных коллег возрастает. Он делает акцент на практических экспериментах, уточняющих вопросах и обсуждениях, позволяющих понять, почему инженеры выбрали те или иные подходы и какие выводы они из этого сделали.
- Сочетайте практику работы с ИИ и наставничество. В материале "What we've learned from Microsoft's own AI transformation" компания описывает практику объединения начинающих инженеров с опытными наставниками и процесс обучения с поддержкой ИИ в рамках программы PRAISE. Также отмечается, что для устойчивых изменений в рабочих процессах командам требовалось совместно учиться и экспериментировать, а сотрудникам — участвовать в определении того, где ИИ приносит реальную пользу, а где по-прежнему необходимо участие человека.
Источник: https://visualstudiomagazine.com/articles/2026/10/08/as-ai-does-the-coding-how-do-human-devs-stay-essential.aspx
Как Разработчикам Оставаться Полезными, Когда Код Пишет ИИ?
По мере того, как ИИ берёт на себя всё большую часть задач по написанию кода, GitHub призывает разработчиков совершенствовать навыки, необходимые для управления этим процессом и принятия решений о том, какой код будет выпущен.
1. Управляйте агентами. Ставьте задачи, предоставляйте контекст и оценивайте результаты. Это процесс координации работы отдельных агентов, отвечающих за аутентификацию, документацию и тестирование, при сохранении за собой ответственности за итоговый результат.
2. Подвергайте результаты сомнению. Попросите другую модель проанализировать и раскритиковать предложенное решение, а затем самостоятельно оцените оба ответа.
3. Решайте более масштабные задачи. Используйте время, высвободившееся от непосредственного написания кода, для анализа потребностей клиентов, архитектуры и выбора оптимальных решений. Разработчикам по-прежнему необходимо определять, действительно ли предлагаемое решение устраняет нужную проблему.
Неудивительно, что этот вопрос, затрагивающий саму суть профессии, всё чаще обсуждается в отрасли, в том числе на крупных конференциях для разработчиков, которые раньше были сосредоточены исключительно на технических аспектах написания кода. На ноябрьской конференции Live!360 Tech Con докладчики обсуждали, что люди должны по-прежнему понимать, оценивать и за что нести ответственность.
Для Анджелы Дуган, вице-президента по поставке продуктов в Red Foundry, ответ кроется в софт-скилах. В своей статье "Human Skills in an AI World: How Technologists Stay Essential" она делает упор на формировании общего понимания задач, критическом пересмотре предположений и умении вовремя остановиться и оценить ситуацию.
Для Дуган это личный вопрос: после того как в июле 2025 года она попала под сокращение в Microsoft, новый работодатель высоко оценил её лидерские качества, способность принимать взвешенные решения, навыки коммуникации и умение справляться со сложными задачами. Она рекомендует использовать ИИ прежде всего для выполнения рутинных задач с низким уровнем риска, высвобождая время сотрудников для деятельности, требующей творческого подхода, выстраивания отношений и определения стратегического направления.
Марк Микейлис из компании IntelliTect подходит к проблеме с точки зрения обучения разработчиков. В статье "Learning to Program in .NET in an AI World" он утверждает, что знания в области архитектуры, структуры кода, тестирования и DevOps остаются необходимыми, даже когда разработчики делегируют написание кода ИИ.
Микейлис предупреждает, что технический долг может накапливаться стремительно, а большое количество тестов, созданных ИИ, не всегда обеспечивает надёжную защиту. Разработчикам необходимо оценивать, действительно ли тесты проверяют то поведение системы, которое от неё ожидается. Он также советует новичкам самостоятельно анализировать сгенерированный код, а затем сравнивать свои выводы с результатами анализа, выполненного ИИ, чтобы понять, что они упустили из виду.
Необходимость в критическом осмыслении распространяется и на вопрос о том, какой объём полномочий следует предоставлять системам ИИ. В статье "The Irresponsible AI Initiative" ведущие DevOps-архитекторы GitHub Микки Гуссет и Кен Мьюз описывают многие сбои в работе ИИ как ошибки проектирования и управления, которых можно было бы избежать. По их мнению, наличие у агента технической возможности получить доступ к информации или выполнить действие вовсе не означает, что ему следует разрешать это делать.
«Промпты не являются границами безопасности», — поясняют они. Их рекомендации включают:
- Обеспечение принципа минимальных привилегий с помощью механизмов контроля, действующих вне модели.
- Тестирование агентов в «песочнице» на устойчивость к вредоносным входным данным, атакам типа «инъекция промпта» (prompt injection) и неоднозначным запросам.
- Поэтапное внедрение функций выполнения реальных действий — с обязательным одобрением человеком, ведением журналов аудита, установкой лимитов на расходы и прав доступа, а также наличием функции немедленной остановки (kill switch).
Они также настаивают на назначении ответственного лица, уполномоченного принимать бизнес-риски, связанные с развёртыванием системы. Эта ответственность остаётся за людьми, даже когда агенты обретают большую автономность.
Дополнительные советы по построению карьеры в эпоху ИИ
- Используйте ИИ в качестве наставника (для новичков). GitHub рекомендует просить Copilot разъяснять концепции и подходы, не выдавая сразу готовые решения. Также предлагается временно отключать функцию автодополнения кода (inline completions) во время обучения, чтобы разработчики учились решать задачи самостоятельно, сохраняя при этом возможность получать помощь через чат.
- Совершенствуйте повседневные инженерные навыки. В статье GitHub "Why developer expertise matters more than ever in the age of AI" подчёркивается, что создание пулл-реквестов, проведение код-ревью и ведение документации служат фундаментом для эффективной работы с использованием ИИ. Практические рекомендации включают в себя ограничение области изменений, обоснование их необходимости, а также самостоятельную проверку и тестирование кода перед отправкой.
- Продолжайте учиться у других разработчиков. В статье Microsoft "AI Is Changing How We Code. It's Also Changing How We Learn" Джеймс Ремпт утверждает, что по мере совершенствования инструментов ИИ ценность непосредственного обучения у опытных коллег возрастает. Он делает акцент на практических экспериментах, уточняющих вопросах и обсуждениях, позволяющих понять, почему инженеры выбрали те или иные подходы и какие выводы они из этого сделали.
- Сочетайте практику работы с ИИ и наставничество. В материале "What we've learned from Microsoft's own AI transformation" компания описывает практику объединения начинающих инженеров с опытными наставниками и процесс обучения с поддержкой ИИ в рамках программы PRAISE. Также отмечается, что для устойчивых изменений в рабочих процессах командам требовалось совместно учиться и экспериментировать, а сотрудникам — участвовать в определении того, где ИИ приносит реальную пользу, а где по-прежнему необходимо участие человека.
Источник: https://visualstudiomagazine.com/articles/2026/10/08/as-ai-does-the-coding-how-do-human-devs-stay-essential.aspx
👎3👍2
День 2813. #ЗаметкиНаПолях #Async
Ещё Раз Про Async/Await в C#. Часть 1
Почему существует асинхронность: цена блокировки потока
Прежде чем углубиться в то, как работает асинхронность, рассмотрим, какую проблему она решает, поскольку обычные объяснения либо расплывчаты («это делает ваше приложение быстрее»), либо неверны («она запускает ваш код в фоновом режиме»).
async/await ничего не ускоряет. Сетевой вызов занимает столько времени, сколько потребуется. Что меняется, так это то, что ваш процесс делает во время ожидания, и это, оказывается, имеет большое значение.
Рассмотрим метод, который вызывает веб-сервис и возвращает длину ответа:
Пока этот запрос находится в работе, вызывающий поток находится внутри блокирующего вызова. Он ничего не делает, он просто занят, и процессор может запускать что-то другое. Но поток не бесплатен. Он всё ещё существует, ему по-прежнему принадлежит свой стек, и, что особенно важно, если он поступил из пула потоков, он больше не доступен для выполнения другой работы.
В блокирующей версии поток занят на все время выполнения ввода-вывода. В асинхронной же версии поток освобождается, как только операция запускается, и поток снова необходим только после того, как станет доступен результат.
Сколько стоит поток
Обычный аргумент против лишних потоков – лишняя память. Поток по умолчанию резервирует мегабайт адресного пространства стека, поэтому десять тысяч потоков резервируют десять гигабайт. Это правда, но в 64-битной версии это не так критично, потому что память резервируется, а не выделяется.
На самом же деле проблемы больше исходят из пула потоков. Пул потоков .NET начинается с количества рабочих потоков, равного числу процессоров. Когда каждый из них занят и в очереди стоит больше работы, это не приводит к немедленному созданию новых. Пул добавляет потоки постепенно, используя алгоритм восхождения к вершине, который отслеживает пропускную способность, чтобы решить, действительно ли помогают дополнительные потоки. Это правильное поведение для работы с нагрузкой на CPU, когда дополнительные потоки, превышающие количество ядер, добавляют только переключения контекста. Это совершенно неправильное поведение для потоков, заблокированных для ввода-вывода, т.к. они не потребляют ресурсы CPU, и никакие измерения пропускной способности не покажут, что добавление потоков как-то исправит ситуацию. В результате заблокированные потоки пула не просто тратят память. Они душат всё остальное, что хочет сделать процесс.
Как это выглядит
Поставим в очередь 1000 операций, каждая из которых ожидает 50мс. Один раз путём блокировки потока, а второй раз путём ожидания:
На машине с 15 ядрами:
Обе версии ожидают по 50мс 1000 раз, и обе в принципе могут завершиться за время чуть более 50мс. Асинхронная версия делает это. Версия с блокировкой занимает в 62 раза больше времени. Пиковое количество потоков одинаково в обоих прогонах. Это то, что обычно удивляет людей. Пул не запустил 1000 потоков для обслуживания блокировки. Он поддерживал примерно один поток на ядро и выполнял операции волнами примерно по 16, поэтому 1000 операций по 50мс превратились в 63 последовательных раунда.
Но это нельзя считать ускорением в 62 раза. Всё зависит от количества повторений. При параллельности 10 на машине с 15 ядрами потоков хватает на всех, поэтому их блокировка не требует никаких измеримых затрат. Async здесь не быстрее, но выделяет почти вдвое больше памяти. Но уже на 100 блокирующая версия примерно в 5 раз медленнее. Асинхронная же версия в каждом случае занимает те же 51мс, т.к. она не блокирует поток для каждой операции.
Таким образом, асинхронность — это не оптимизация производительности в обычном понимании. Это масштабируемость, которая:
- не уменьшает задержку для единственной операции, даже немного добавляет;
- не уменьшает выделение памяти, а добавляет;
- лишь устраняет связь между количеством выполняемых операций и количеством необходимых вам потоков.
Для настольного приложения, загружающего 3 файла при запуске, это очевидно, не стоит того, и синхронный ввод-вывод может быть лучшим выбором. Для сервера, обрабатывающего 1000+ одновременных запросов, это разница между работой и зависанием.
Другая причина: отзывчивость
WinForms, WPF и MAUI обрабатывают цикл обработки сообщений в одном выделенном потоке. Блокировка этого потока останавливает цикл сообщений, что означает, что окно перестаёт перерисовываться и перестаёт отвечать на ввод. Такой поток существует только один, поэтому вариант «просто использовать другой» невозможен: работа должна покинуть UI-поток, а результат должен вернуться в него. async/await делает и то, и другое (как - об этом позже).
Продолжение следует...
Источник: https://www.meziantou.net/why-async-exists-the-cost-of-blocking-a-thread.htm
Ещё Раз Про Async/Await в C#. Часть 1
Почему существует асинхронность: цена блокировки потока
Прежде чем углубиться в то, как работает асинхронность, рассмотрим, какую проблему она решает, поскольку обычные объяснения либо расплывчаты («это делает ваше приложение быстрее»), либо неверны («она запускает ваш код в фоновом режиме»).
async/await ничего не ускоряет. Сетевой вызов занимает столько времени, сколько потребуется. Что меняется, так это то, что ваш процесс делает во время ожидания, и это, оказывается, имеет большое значение.
Рассмотрим метод, который вызывает веб-сервис и возвращает длину ответа:
public static int GetLength(string url)
{
using var client = new HttpClient();
var response = client
.Send(new HttpRequestMessage(HttpMethod.Get, url));
return (int)response
.Content
.Headers
.ContentLength
.GetValueOrDefault();
}
Пока этот запрос находится в работе, вызывающий поток находится внутри блокирующего вызова. Он ничего не делает, он просто занят, и процессор может запускать что-то другое. Но поток не бесплатен. Он всё ещё существует, ему по-прежнему принадлежит свой стек, и, что особенно важно, если он поступил из пула потоков, он больше не доступен для выполнения другой работы.
В блокирующей версии поток занят на все время выполнения ввода-вывода. В асинхронной же версии поток освобождается, как только операция запускается, и поток снова необходим только после того, как станет доступен результат.
Сколько стоит поток
Обычный аргумент против лишних потоков – лишняя память. Поток по умолчанию резервирует мегабайт адресного пространства стека, поэтому десять тысяч потоков резервируют десять гигабайт. Это правда, но в 64-битной версии это не так критично, потому что память резервируется, а не выделяется.
На самом же деле проблемы больше исходят из пула потоков. Пул потоков .NET начинается с количества рабочих потоков, равного числу процессоров. Когда каждый из них занят и в очереди стоит больше работы, это не приводит к немедленному созданию новых. Пул добавляет потоки постепенно, используя алгоритм восхождения к вершине, который отслеживает пропускную способность, чтобы решить, действительно ли помогают дополнительные потоки. Это правильное поведение для работы с нагрузкой на CPU, когда дополнительные потоки, превышающие количество ядер, добавляют только переключения контекста. Это совершенно неправильное поведение для потоков, заблокированных для ввода-вывода, т.к. они не потребляют ресурсы CPU, и никакие измерения пропускной способности не покажут, что добавление потоков как-то исправит ситуацию. В результате заблокированные потоки пула не просто тратят память. Они душат всё остальное, что хочет сделать процесс.
Как это выглядит
Поставим в очередь 1000 операций, каждая из которых ожидает 50мс. Один раз путём блокировки потока, а второй раз путём ожидания:
// block: Thread.Sleep удерживает поток
tasks[i] = Task.Run(() => Thread.Sleep(time));
// async: Task.Delay освобождает поток
tasks[i] = Task.Delay(time);
На машине с 15 ядрами:
Block
- всего времени: 3307мс
- максимум потоков: 16
Async
- всего времени: 53мс
- максимум потоков: 16
Обе версии ожидают по 50мс 1000 раз, и обе в принципе могут завершиться за время чуть более 50мс. Асинхронная версия делает это. Версия с блокировкой занимает в 62 раза больше времени. Пиковое количество потоков одинаково в обоих прогонах. Это то, что обычно удивляет людей. Пул не запустил 1000 потоков для обслуживания блокировки. Он поддерживал примерно один поток на ядро и выполнял операции волнами примерно по 16, поэтому 1000 операций по 50мс превратились в 63 последовательных раунда.
Но это нельзя считать ускорением в 62 раза. Всё зависит от количества повторений. При параллельности 10 на машине с 15 ядрами потоков хватает на всех, поэтому их блокировка не требует никаких измеримых затрат. Async здесь не быстрее, но выделяет почти вдвое больше памяти. Но уже на 100 блокирующая версия примерно в 5 раз медленнее. Асинхронная же версия в каждом случае занимает те же 51мс, т.к. она не блокирует поток для каждой операции.
Таким образом, асинхронность — это не оптимизация производительности в обычном понимании. Это масштабируемость, которая:
- не уменьшает задержку для единственной операции, даже немного добавляет;
- не уменьшает выделение памяти, а добавляет;
- лишь устраняет связь между количеством выполняемых операций и количеством необходимых вам потоков.
Для настольного приложения, загружающего 3 файла при запуске, это очевидно, не стоит того, и синхронный ввод-вывод может быть лучшим выбором. Для сервера, обрабатывающего 1000+ одновременных запросов, это разница между работой и зависанием.
Другая причина: отзывчивость
WinForms, WPF и MAUI обрабатывают цикл обработки сообщений в одном выделенном потоке. Блокировка этого потока останавливает цикл сообщений, что означает, что окно перестаёт перерисовываться и перестаёт отвечать на ввод. Такой поток существует только один, поэтому вариант «просто использовать другой» невозможен: работа должна покинуть UI-поток, а результат должен вернуться в него. async/await делает и то, и другое (как - об этом позже).
Продолжение следует...
Источник: https://www.meziantou.net/why-async-exists-the-cost-of-blocking-a-thread.htm
👍16
День 2816. #Карьера #AI
Станет ли ИИ-Кодинг Концом Программирования для Людей?
Автор оригинала: Pavel Yosifovich
Мое знакомство с компьютерами состоялось в 1983 г. В ZX-Spectrum я мог использовать язык программирования (BASIC), чтобы заставить компьютер выполнять различные задачи, создавать игры и другие интересные программы. Важной частью процесса было именно удовольствие — создание чего-то из ничего; это было увлекательно и бросало вызов, напоминая игру, независимо от того, что я писал. Вскоре у меня появился Commodore 64. Хотя BASIC оставался основным языком, нередко приходилось писать код и на ассемблере. Главной причиной была низкая скорость BASIC (он был интерпретируемым).
Прошли годы — и BASIC ушёл в прошлое. Ассемблер тоже практически не используется. Популярные языки справляются с большинством задач. Даже интерпретируемые языки вроде Python и JavaScript пользуются огромным успехом: пусть они и «медленные», их быстродействия хватает для множества приложений. С появлением языков программирования, предлагающих более высокие уровни абстракции, потребность в ассемблере свелась к минимуму.
Эра ИИ-программирования
Вы наверняка видели возможности ИИ в написании кода и, вероятно, уже пользовались ими. Признаюсь, мне немного грустно от того, что люди будут писать меньше кода вручную, перепоручая эту задачу ИИ-агентам. Возможно, это естественный эволюционный процесс — переход к использованию абстракций более высокого уровня. Однако здесь есть принципиальное различие. Раньше под более высокой абстракцией обычно подразумевался другой язык программирования — возможно, с более совершенными инструментами и средой разработки.
ИИ-кодинг — процесс особый: мы используем естественный язык, который по своей природе допускает неоднозначность и неточность, — а ведь именно для устранения этих проблем в своё время и создавались языки программирования. ИИ активно используется для написания кода, причем степень участия человека варьируется от существенной до отсутствующей; однако ответственность за качество кода (как бы оно ни определялось для конкретной кодовой базы) по-прежнему лежит на человеке — по крайней мере, пока.
Классические споры энтузиастов разных языков программирования — C# или Java? Python или Ruby? C или C++? — похоже, теряют свою актуальность. Так ли необходимо знать о последних новинках в C#? Если код генерирует ИИ, а мы лишь проводим код-ревью (если проводим), важны ли нам тонкости самого процесса написания? Сторонники использования ИИ в программировании, вероятно, видят будущее (причём не столь отдаленное), в котором ИИ будет писать весь код на основе спецификаций, составленных людьми или даже самим ИИ, причём сам код может быть скрыт от человеческих глаз. Но почему бы не пойти ещё дальше? Возможно, ИИ сможет создать более эффективный язык — ведь использование текстовых токенов удобно лишь для людей; быть может, более компактный и производительный бинарный язык позволил бы ИИ писать код ещё быстрее.
Один из аргументов против такой модели заключается в том, что неизбежно будут возникать ошибки, а значит, участие человека всё же необходимо. ИИ не безошибочен — по крайней мере, пока. В коде могут встречаться состояния гонки, проблемы с памятью, логические ошибки или проблемы с производительностью. Именно здесь требуется вмешательство человека: чтобы исправить ошибку, нужно понимать код. С другой стороны, ИИ способен помочь и в устранении ошибок — он уже это делает. Достаточно дать промпт, предоставить логи и т.д., и ИИ с готовностью возьмётся за поиск и исправление багов. Гарантирован ли успех? Пока нет, но однажды ИИ сможет устранять ошибки самостоятельно — возможно, лишь изредка прибегая к помощи человека. Даже в тех случаях, когда требуется вмешательство человека, «видеть» сам код может и не понадобиться. ИИ сам внесёт исправления: если ожидаемое поведение системы обеспечено и целевые показатели производительности достигнуты, то какая разница, как выглядит код? Конечно, возможны исключительные ситуации, когда ошибки не удается устранить таким способом. Но ИИ всегда способен преобразовать бинарный код в вид, понятный человеку, чтобы тот мог детально изучить проблему.
Кому-то подобные перспективы могут показаться чрезмерными. Неужели мы действительно полностью переложим программирование на плечи машин? Возможно, история сможет нас чему-то научить.
До появления карманных калькуляторов учёные прибегали к помощи людей-«вычислителей», которые выполняли рутинную работу, в то время как сам учёный сосредотачивался на «научной» составляющей. Потом роль таких помощников исчезла — мы безоговорочно доверяем технике. Когда мы пишем код на языке высокого уровня, мы используем компилятор для его преобразования в машинный код, понятный физическому процессору. В компиляторах случаются ошибки, но мы всё равно им доверяем. Ошибку в компиляторе рано или поздно исправят. Почему бы не рассматривать написание кода с помощью ИИ в том же ключе? ИИ может ошибаться, как и компиляторы, но он также способен исправить эти ошибки после их обнаружения. Нам не обязательно видеть сам код.
Конец эпохи ручного программирования?
Означает ли это, что людям больше не нужно писать код вручную? Полагаю, мы движемся именно в этом направлении. Кажется, что участие человека в написании кода больше не требуется. Неудивительно, что многие программисты всерьез опасаются, что ИИ «отнимет» у них работу. Что же ждет программиста-человека в будущем?
Недавно я в прямом эфире демонстрировал процесс написания драйвера ядра Windows с нуля — задачи, требующей учёта множества нюансов. Один из участников спросил, обязательно ли знать все эти детали: ведь ИИ наверняка может написать код вместо меня. Возможно, ИИ справится с написанием кода даже лучше меня. Однако я делал упор не на деталях реализации, а на архитектуре и концепциях, лежащих в основе кода: как работает драйвер ядра? Как он загружается? Как управляет ресурсами? Как взаимодействует с ядром? И т.д. Суть в том, что детали реализации кода можно доверить ИИ, но фундаментальные концепции — нет. Во-первых, как человек может провести проверку кода, написанного ИИ? Даже если предположить, что ИИ пишет код без ошибок, как быть с общей архитектурой и замыслом проекта? ИИ может не видеть всей картины целиком. И даже поручив написание всего кода ИИ, всё равно придётся объяснить ему, что именно нужно создать. Какова роль драйвера в этом проекте? Как он должен взаимодействовать с пользователем? Какие логи использовать? Здесь возможны разные варианты, и именно поэтому человеку необходимо глубокое понимание предметной области, чтобы составить качественное техническое задание для ИИ. Человек становится проектировщиком и архитектором, делегируя рутинную работу и технические детали ИИ.
Этот постепенный переход — длительный процесс. Он не произойдет за месяц, но и десятилетия на это не уйдут. Темпы развития ИИ трудно предсказать, поскольку они, похоже, носят экспоненциальный характер, а человеческая природа не приспособлена к восприятию таких процессов — мы мыслим линейно.
Итого
В глубине души мне грустно от такого положения дел. Мне по-прежнему нравится писать код вручную, создавая то, что я могу назвать своим творением. Все мои знания тонкостей тех или иных языков программирования со временем устареют. Стоит ли изучать новейшие версии языков программирования? Пока да. Но полагаю, что в недалёком будущем это изменится.
Если код моих приложений пишет ИИ, можно ли считать этот код моим? А может, это неверная постановка вопроса. «Моим» является замысел приложения, его архитектура, функционал, возможности и дизайн. Сам же код — это лишь деталь реализации, с чем лучше справляются машины.
Источник: https://scorpiosoftware.net/2026/09/21/is-ai-coding-the-end-of-coding/
Станет ли ИИ-Кодинг Концом Программирования для Людей?
Автор оригинала: Pavel Yosifovich
Мое знакомство с компьютерами состоялось в 1983 г. В ZX-Spectrum я мог использовать язык программирования (BASIC), чтобы заставить компьютер выполнять различные задачи, создавать игры и другие интересные программы. Важной частью процесса было именно удовольствие — создание чего-то из ничего; это было увлекательно и бросало вызов, напоминая игру, независимо от того, что я писал. Вскоре у меня появился Commodore 64. Хотя BASIC оставался основным языком, нередко приходилось писать код и на ассемблере. Главной причиной была низкая скорость BASIC (он был интерпретируемым).
Прошли годы — и BASIC ушёл в прошлое. Ассемблер тоже практически не используется. Популярные языки справляются с большинством задач. Даже интерпретируемые языки вроде Python и JavaScript пользуются огромным успехом: пусть они и «медленные», их быстродействия хватает для множества приложений. С появлением языков программирования, предлагающих более высокие уровни абстракции, потребность в ассемблере свелась к минимуму.
Эра ИИ-программирования
Вы наверняка видели возможности ИИ в написании кода и, вероятно, уже пользовались ими. Признаюсь, мне немного грустно от того, что люди будут писать меньше кода вручную, перепоручая эту задачу ИИ-агентам. Возможно, это естественный эволюционный процесс — переход к использованию абстракций более высокого уровня. Однако здесь есть принципиальное различие. Раньше под более высокой абстракцией обычно подразумевался другой язык программирования — возможно, с более совершенными инструментами и средой разработки.
ИИ-кодинг — процесс особый: мы используем естественный язык, который по своей природе допускает неоднозначность и неточность, — а ведь именно для устранения этих проблем в своё время и создавались языки программирования. ИИ активно используется для написания кода, причем степень участия человека варьируется от существенной до отсутствующей; однако ответственность за качество кода (как бы оно ни определялось для конкретной кодовой базы) по-прежнему лежит на человеке — по крайней мере, пока.
Классические споры энтузиастов разных языков программирования — C# или Java? Python или Ruby? C или C++? — похоже, теряют свою актуальность. Так ли необходимо знать о последних новинках в C#? Если код генерирует ИИ, а мы лишь проводим код-ревью (если проводим), важны ли нам тонкости самого процесса написания? Сторонники использования ИИ в программировании, вероятно, видят будущее (причём не столь отдаленное), в котором ИИ будет писать весь код на основе спецификаций, составленных людьми или даже самим ИИ, причём сам код может быть скрыт от человеческих глаз. Но почему бы не пойти ещё дальше? Возможно, ИИ сможет создать более эффективный язык — ведь использование текстовых токенов удобно лишь для людей; быть может, более компактный и производительный бинарный язык позволил бы ИИ писать код ещё быстрее.
Один из аргументов против такой модели заключается в том, что неизбежно будут возникать ошибки, а значит, участие человека всё же необходимо. ИИ не безошибочен — по крайней мере, пока. В коде могут встречаться состояния гонки, проблемы с памятью, логические ошибки или проблемы с производительностью. Именно здесь требуется вмешательство человека: чтобы исправить ошибку, нужно понимать код. С другой стороны, ИИ способен помочь и в устранении ошибок — он уже это делает. Достаточно дать промпт, предоставить логи и т.д., и ИИ с готовностью возьмётся за поиск и исправление багов. Гарантирован ли успех? Пока нет, но однажды ИИ сможет устранять ошибки самостоятельно — возможно, лишь изредка прибегая к помощи человека. Даже в тех случаях, когда требуется вмешательство человека, «видеть» сам код может и не понадобиться. ИИ сам внесёт исправления: если ожидаемое поведение системы обеспечено и целевые показатели производительности достигнуты, то какая разница, как выглядит код? Конечно, возможны исключительные ситуации, когда ошибки не удается устранить таким способом. Но ИИ всегда способен преобразовать бинарный код в вид, понятный человеку, чтобы тот мог детально изучить проблему.
Кому-то подобные перспективы могут показаться чрезмерными. Неужели мы действительно полностью переложим программирование на плечи машин? Возможно, история сможет нас чему-то научить.
До появления карманных калькуляторов учёные прибегали к помощи людей-«вычислителей», которые выполняли рутинную работу, в то время как сам учёный сосредотачивался на «научной» составляющей. Потом роль таких помощников исчезла — мы безоговорочно доверяем технике. Когда мы пишем код на языке высокого уровня, мы используем компилятор для его преобразования в машинный код, понятный физическому процессору. В компиляторах случаются ошибки, но мы всё равно им доверяем. Ошибку в компиляторе рано или поздно исправят. Почему бы не рассматривать написание кода с помощью ИИ в том же ключе? ИИ может ошибаться, как и компиляторы, но он также способен исправить эти ошибки после их обнаружения. Нам не обязательно видеть сам код.
Конец эпохи ручного программирования?
Означает ли это, что людям больше не нужно писать код вручную? Полагаю, мы движемся именно в этом направлении. Кажется, что участие человека в написании кода больше не требуется. Неудивительно, что многие программисты всерьез опасаются, что ИИ «отнимет» у них работу. Что же ждет программиста-человека в будущем?
Недавно я в прямом эфире демонстрировал процесс написания драйвера ядра Windows с нуля — задачи, требующей учёта множества нюансов. Один из участников спросил, обязательно ли знать все эти детали: ведь ИИ наверняка может написать код вместо меня. Возможно, ИИ справится с написанием кода даже лучше меня. Однако я делал упор не на деталях реализации, а на архитектуре и концепциях, лежащих в основе кода: как работает драйвер ядра? Как он загружается? Как управляет ресурсами? Как взаимодействует с ядром? И т.д. Суть в том, что детали реализации кода можно доверить ИИ, но фундаментальные концепции — нет. Во-первых, как человек может провести проверку кода, написанного ИИ? Даже если предположить, что ИИ пишет код без ошибок, как быть с общей архитектурой и замыслом проекта? ИИ может не видеть всей картины целиком. И даже поручив написание всего кода ИИ, всё равно придётся объяснить ему, что именно нужно создать. Какова роль драйвера в этом проекте? Как он должен взаимодействовать с пользователем? Какие логи использовать? Здесь возможны разные варианты, и именно поэтому человеку необходимо глубокое понимание предметной области, чтобы составить качественное техническое задание для ИИ. Человек становится проектировщиком и архитектором, делегируя рутинную работу и технические детали ИИ.
Этот постепенный переход — длительный процесс. Он не произойдет за месяц, но и десятилетия на это не уйдут. Темпы развития ИИ трудно предсказать, поскольку они, похоже, носят экспоненциальный характер, а человеческая природа не приспособлена к восприятию таких процессов — мы мыслим линейно.
Итого
В глубине души мне грустно от такого положения дел. Мне по-прежнему нравится писать код вручную, создавая то, что я могу назвать своим творением. Все мои знания тонкостей тех или иных языков программирования со временем устареют. Стоит ли изучать новейшие версии языков программирования? Пока да. Но полагаю, что в недалёком будущем это изменится.
Если код моих приложений пишет ИИ, можно ли считать этот код моим? А может, это неверная постановка вопроса. «Моим» является замысел приложения, его архитектура, функционал, возможности и дизайн. Сам же код — это лишь деталь реализации, с чем лучше справляются машины.
Источник: https://scorpiosoftware.net/2026/09/21/is-ai-coding-the-end-of-coding/
👍6👎2
День 2817. #ЧтоНовенького #VS
Прекращаем Сборку После Первой Ошибки
Мэдс Кристенсен продолжает поставлять небольшие и на первый взгляд очевидные улучшения процесса разработки в Visual Studio.
Если один из проектов в решении не удается собрать, процесс сборки всего решения обычно продолжается. У вас уже есть ошибка, которую нужно исправить, но остальные проекты продолжают собираться, несмотря на то что сборка решения всё равно закончится неудачей.
В VS2026 (версии 18.11) появилась опция, позволяющая автоматически останавливать сборку решения сразу после того, как в одном из проектов произошел сбой. Если ваш следующий шаг — исправление этой ошибки, то такая настройка поможет сэкономить время, прекращая дальнейший процесс сборки.
Настройка остановки сборки
Перейдите в раздел Tools > Options > Projects and Solutions > Build and Run (Сервис > Параметры > Проекты и решения > Сборка и запуск) и включите параметр Stop solution build after first failure (Останавливать сборку решения после первой ошибки).
При возникновении ошибки в проекте Visual Studio перестанет запускать задачи для проектов, стоящих в очереди, и безопасно остановит те, что уже находятся в процессе сборки. При этом результат сборки помечается как «сбой» (failed), а не «отмена» (canceled). Вам больше не нужно следить за появлением первой ошибки и вручную прерывать выполнение остальных задач.
Эта функция останавливает сборку всего решения, а не только тех проектов, которые зависят от проблемного компонента. Это важное отличие: вы быстрее переходите к устранению первопричины, но при этом не получаете результаты сборки остальных проектов, входящих в решение.
Если вы предпочитаете видеть результаты по всем проектам, прежде чем приступать к исправлению ошибок, оставьте эту настройку отключенной — в таком случае сохранится привычное поведение системы. При необходимости функцию всегда можно отключить на той же странице параметров.
Итоговая экономия времени зависит от структуры вашего решения и того, на каком этапе произошёл сбой. В решениях с длинными цепочками зависимостей или большим объёмом параллельных задач экономия времени будет более заметной, чем в небольших проектах, сборка которых и так завершается практически мгновенно.
Данная настройка определяет, когда следует остановить процесс сборки в случае возникновения ошибки. Она не влияет на то, какие именно проекты будут включены в сборку. Существующие настройки Build and Run (Сборка и запуск) уже позволяют задать, например, сборку только запускаемых проектов и их зависимостей при выполнении или отладке.
Попробуйте использовать функцию остановки при сбое для решения, в котором вы обычно отменяете сборку после появления ошибки. Сравните время ожидания после возникновения сбоя и выберите тот подход, который лучше всего соответствует вашему процессу поиска и устранения неполадок.
Источник: https://devblogs.microsoft.com/visualstudio/stop-waiting-on-a-failed-build/
Прекращаем Сборку После Первой Ошибки
Мэдс Кристенсен продолжает поставлять небольшие и на первый взгляд очевидные улучшения процесса разработки в Visual Studio.
Если один из проектов в решении не удается собрать, процесс сборки всего решения обычно продолжается. У вас уже есть ошибка, которую нужно исправить, но остальные проекты продолжают собираться, несмотря на то что сборка решения всё равно закончится неудачей.
В VS2026 (версии 18.11) появилась опция, позволяющая автоматически останавливать сборку решения сразу после того, как в одном из проектов произошел сбой. Если ваш следующий шаг — исправление этой ошибки, то такая настройка поможет сэкономить время, прекращая дальнейший процесс сборки.
Настройка остановки сборки
Перейдите в раздел Tools > Options > Projects and Solutions > Build and Run (Сервис > Параметры > Проекты и решения > Сборка и запуск) и включите параметр Stop solution build after first failure (Останавливать сборку решения после первой ошибки).
При возникновении ошибки в проекте Visual Studio перестанет запускать задачи для проектов, стоящих в очереди, и безопасно остановит те, что уже находятся в процессе сборки. При этом результат сборки помечается как «сбой» (failed), а не «отмена» (canceled). Вам больше не нужно следить за появлением первой ошибки и вручную прерывать выполнение остальных задач.
Эта функция останавливает сборку всего решения, а не только тех проектов, которые зависят от проблемного компонента. Это важное отличие: вы быстрее переходите к устранению первопричины, но при этом не получаете результаты сборки остальных проектов, входящих в решение.
Если вы предпочитаете видеть результаты по всем проектам, прежде чем приступать к исправлению ошибок, оставьте эту настройку отключенной — в таком случае сохранится привычное поведение системы. При необходимости функцию всегда можно отключить на той же странице параметров.
Итоговая экономия времени зависит от структуры вашего решения и того, на каком этапе произошёл сбой. В решениях с длинными цепочками зависимостей или большим объёмом параллельных задач экономия времени будет более заметной, чем в небольших проектах, сборка которых и так завершается практически мгновенно.
Данная настройка определяет, когда следует остановить процесс сборки в случае возникновения ошибки. Она не влияет на то, какие именно проекты будут включены в сборку. Существующие настройки Build and Run (Сборка и запуск) уже позволяют задать, например, сборку только запускаемых проектов и их зависимостей при выполнении или отладке.
Попробуйте использовать функцию остановки при сбое для решения, в котором вы обычно отменяете сборку после появления ошибки. Сравните время ожидания после возникновения сбоя и выберите тот подход, который лучше всего соответствует вашему процессу поиска и устранения неполадок.
Источник: https://devblogs.microsoft.com/visualstudio/stop-waiting-on-a-failed-build/
👍1👎1
День 2818. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
50. Фреймворки для создания тестовых заглушек
«Расскажите, как использовать тестовые заглушки для модульного тестирования? Приведите примеры создания заглушек для зависимостей и проверки того, что код ведёт себя ожидаемым образом».
Хороший ответ
Использование фреймворков, вроде Moq или NSubstitute в .NET-приложениях для модульного тестирования предполагает создание заглушек для зависимостей, которые могут быть у класса. Это позволяет всесторонне протестировать класс в изоляции.
Тестовые заглушки – общий термин, описывающий все разновидности фиктивных зависимостей в тестах. Среди них можно выделить два основных:
- Моки (mock) – позволяют эмулировать и проверять выходные взаимодействия, т.е. вызовы, совершаемые тестируемой системой к её зависимостям для изменения их состояния. Например, моком можно заменить почтовый сервер, на который отправляется письмо.
- Стабы (stub) – помогают эмулировать входные взаимодействия, т.е. вызовы к зависимостям для получения входных данных. Например, стабом можно заменить базу, из которой запрашиваются данные. Стабы настраиваются для возвращения разных значений для разных сценариев.
Вот пример использования фреймворка NSubstitute:
NSubstitute позволяет проверять, что методы мок-объекта были вызваны с ожидаемыми аргументами. Это важно для того, чтобы убедиться, что тестируемый класс правильно взаимодействует со своими зависимостями. Для этого в NSubstitute используется метод
Рекомендации
1. Проверка поведения: важно всегда проверять поведение, а не детали реализации. Т.е. фокусироваться на том, что возвращает мок или его влияние на результат, а не на том, как именно он это делает.
Замечание: не стоит проверять взаимодействие со стабами. В примере выше ICalculator по сути является стабом. Он предоставляет входные значения для сервиса. Проверка
При этом заглушка может быть моком и стабом одновременно, когда в тесте задаётся поведение одного метода зависимости, а проверяется взаимодействие с другим методом. Например, при тестировании сервиса бронирования такой заглушкой может быть репозиторий, где задаётся поведение метода проверки наличия свободных мест, а проверяется, что сервис бронирования вызывает метод резервации места в репозитории.
2. Один мок на тест: в идеале для каждого теста следует создавать отдельный мок-объект. Это помогает избежать взаимного влияния тестов друг на друга и делает их более понятными.
3. Чёткая настройка взаимодействий: важно явно задавать взаимодействия с моками и проверять их — это обеспечит ясность тестов и упростит их дальнейшее сопровождение.
Распространённые ошибки
- Чрезмерное использование заглушек: делает тесты хрупкими и излишне сложными. Заменять стоит только те зависимости, которые являются внешними для тестируемого класса. Использование заглушек для приватных зависимостей, простых структур данных или сущностей обычно приводит к тому, что тесты ломаются при малейшем изменении структуры данных.
И даже внешние зависимости не всегда стоит заменять заглушками. Например, база данных, которая используется только вашим приложением, и к которой не имеют доступа внешние системы, полностью скрыта от клиентов вашей системы. Её даже можно заменить другой базой, и никто не заметит. Здесь вместо замены базы заглушкой в тестах можно рассмотреть тестирование с использованием БД в памяти.
- Игнорирование реальных реализаций: заменяя слишком много объектов — особенно те, что содержат бизнес-логику, которую тоже следовало бы протестировать, — вы рискуете пропустить серьёзные проблемы интеграции или логические ошибки в реальном коде.
- Мокирование конкретных классов: Moq позволят задать поведение отдельных методов зависимости, при этом вызывая методы реального объекта, поведение которых не было задано. Например:
Настройка
Это один из антипаттернов тестирования. Если вам приходится так делать, это часто означает, что класс зависимости объединяет две ответственности, и стоит его разделить.
Источники:
- https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
- «Принципы юнит-тестирования» (Владимир Хориков — СПб.: Питер, 2022).
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
50. Фреймворки для создания тестовых заглушек
«Расскажите, как использовать тестовые заглушки для модульного тестирования? Приведите примеры создания заглушек для зависимостей и проверки того, что код ведёт себя ожидаемым образом».
Хороший ответ
Использование фреймворков, вроде Moq или NSubstitute в .NET-приложениях для модульного тестирования предполагает создание заглушек для зависимостей, которые могут быть у класса. Это позволяет всесторонне протестировать класс в изоляции.
Тестовые заглушки – общий термин, описывающий все разновидности фиктивных зависимостей в тестах. Среди них можно выделить два основных:
- Моки (mock) – позволяют эмулировать и проверять выходные взаимодействия, т.е. вызовы, совершаемые тестируемой системой к её зависимостям для изменения их состояния. Например, моком можно заменить почтовый сервер, на который отправляется письмо.
- Стабы (stub) – помогают эмулировать входные взаимодействия, т.е. вызовы к зависимостям для получения входных данных. Например, стабом можно заменить базу, из которой запрашиваются данные. Стабы настраиваются для возвращения разных значений для разных сценариев.
Вот пример использования фреймворка NSubstitute:
using NSubstitute;
using Xunit;
public class ServiceTests
{
private IService _sut;
private ICalculator _calc;
public ServiceTests()
{
// Создаём заглушку для зависимости ICalculator
_calc = Substitute.For<ICalculator>();
_sut = new Service(_calc);
}
[Fact]
public void AddNumbers_ShouldReturnCorrectSum()
{
// Задаём поведение метода Add
_calc.Add(5, 10).Returns(15);
var result = _sut.AddNumbers(5, 10);
Assert.Equal(15, result);
// Проверяем, что Add был вызван
_calc.Received(1).Add(5, 10);
}
}
NSubstitute позволяет проверять, что методы мок-объекта были вызваны с ожидаемыми аргументами. Это важно для того, чтобы убедиться, что тестируемый класс правильно взаимодействует со своими зависимостями. Для этого в NSubstitute используется метод
Received(), как показано в примере выше.Рекомендации
1. Проверка поведения: важно всегда проверять поведение, а не детали реализации. Т.е. фокусироваться на том, что возвращает мок или его влияние на результат, а не на том, как именно он это делает.
Замечание: не стоит проверять взаимодействие со стабами. В примере выше ICalculator по сути является стабом. Он предоставляет входные значения для сервиса. Проверка
Received() показана для примера, но она излишняя и делает тест хрупким. Тест не должен интересоваться, как тестируемая система получает результат, если результат верный.При этом заглушка может быть моком и стабом одновременно, когда в тесте задаётся поведение одного метода зависимости, а проверяется взаимодействие с другим методом. Например, при тестировании сервиса бронирования такой заглушкой может быть репозиторий, где задаётся поведение метода проверки наличия свободных мест, а проверяется, что сервис бронирования вызывает метод резервации места в репозитории.
2. Один мок на тест: в идеале для каждого теста следует создавать отдельный мок-объект. Это помогает избежать взаимного влияния тестов друг на друга и делает их более понятными.
3. Чёткая настройка взаимодействий: важно явно задавать взаимодействия с моками и проверять их — это обеспечит ясность тестов и упростит их дальнейшее сопровождение.
Распространённые ошибки
- Чрезмерное использование заглушек: делает тесты хрупкими и излишне сложными. Заменять стоит только те зависимости, которые являются внешними для тестируемого класса. Использование заглушек для приватных зависимостей, простых структур данных или сущностей обычно приводит к тому, что тесты ломаются при малейшем изменении структуры данных.
И даже внешние зависимости не всегда стоит заменять заглушками. Например, база данных, которая используется только вашим приложением, и к которой не имеют доступа внешние системы, полностью скрыта от клиентов вашей системы. Её даже можно заменить другой базой, и никто не заметит. Здесь вместо замены базы заглушкой в тестах можно рассмотреть тестирование с использованием БД в памяти.
- Игнорирование реальных реализаций: заменяя слишком много объектов — особенно те, что содержат бизнес-логику, которую тоже следовало бы протестировать, — вы рискуете пропустить серьёзные проблемы интеграции или логические ошибки в реальном коде.
- Мокирование конкретных классов: Moq позволят задать поведение отдельных методов зависимости, при этом вызывая методы реального объекта, поведение которых не было задано. Например:
var moq = new Mock<Calculator> {CallBase = true };
// метод Add должен быть виртуальным
moq.Setup(x => x.Add(5, 10)).Returns(15);Настройка
CallBase = true позволяет моку сохранить реальное поведение базового класса, если оно не было переопределено. Таким образом вызов moq.Add(5, 10) в тесте будет использовать заданное поведение, а вызов, например, moq.Multiply(5, 10) будет использовать метод реального объекта Calculator. Так можно, к примеру, задать заглушки только для методов класса, меняющих состояние системы (пишущих в базу), при этом вызывая реальные методы, не меняющие состояния (читающие из базы).Это один из антипаттернов тестирования. Если вам приходится так делать, это часто означает, что класс зависимости объединяет две ответственности, и стоит его разделить.
Источники:
- https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
- «Принципы юнит-тестирования» (Владимир Хориков — СПб.: Питер, 2022).
👎2
День 2819. #ЗаметкиНаПолях #Async
Ещё Раз Про Async/Await в C#. Часть 4
Паттерн awaitable: создание объектов, поддерживающих ожидание
Если спросить большинство C#-разработчиков, что можно использовать с оператором
Понимание этой структуры поможет разобраться в конечном автомате, который генерируется компилятором (о нём позже). Кроме того, это позволит создавать собственные «ожидаемые» объекты.
Структура
Чтобы выражение
Вот и весь контракт. Компилятор выполняет привязку этих членов по их именам, что имеет два важных следствия:
1. Метод
2. Объект ожидания может быть структурой. TaskAwaiter, ValueTaskAwaiter и YieldAwaitable.YieldAwaiter являются структурами; поэтому при ожидании уже завершённой задачи не происходит выделения памяти под объект ожидания.
Как используются эти три члена
Для каждого вызова
- Свойство
- Метод
- Метод
То, что
ICriticalNotifyCompletion
Существует еще один интерфейс —
Слово «Unsafe» звучит тревожно, но на деле всё не так страшно: создаваемый компилятором конечный автомат уже захватывает
Реализуйте оба интерфейса. В
Полезный объект, поддерживающий ожидание
Особенности:
- Тип сам является своим собственным «ожидателем».
- Свойство
- Это readonly-структура. Для операции ожидания не требуется выделение памяти в куче.
Примечание: Тот же механизм, только наоборот, позволяет в UI-фреймворках использовать конструкцию
Когда стоит создавать свой объект ожидания
Довольно редко. В большинстве случаев правильным выбором будут
1. Вы хотите, чтобы
2. Вы работаете с механизмом завершения операций, который существенно отличается от стандартного, и хотите избежать выделения памяти под объект
3. Вы хотите сделать существующий тип «ожидаемым», не меняя его исходный код — здесь поможет прием с методами расширения.
Важно помнить: собственный объект ожидания не поддерживает композицию. Он не будет работать с
Источник: https://www.meziantou.net/the-awaitable-pattern-writing-your-own-awaitable.htm
Ещё Раз Про Async/Await в C#. Часть 4
Паттерн awaitable: создание объектов, поддерживающих ожидание
Если спросить большинство C#-разработчиков, что можно использовать с оператором
await, они ответят: «Task». Это распространённый, но далеко не единственный случай. Механизм await работает так же, как и foreach, на основе структуры. Компилятор ищет определенную «форму» (набор элементов), и Task как раз ей соответствует.Понимание этой структуры поможет разобраться в конечном автомате, который генерируется компилятором (о нём позже). Кроме того, это позволит создавать собственные «ожидаемые» объекты.
Структура
Чтобы выражение
await expr успешно скомпилировалось, у объекта expr должен быть метод GetAwaiter(). Возвращаемый им тип — так называемый «awaiter» (объект ожидания) — должен содержать три элемента и реализовывать один интерфейс:public readonly struct MyAwaiter : INotifyCompletion
{
// завершилась ли уже операция?
public bool IsCompleted { get; }
// значение, либо повторный выброс исключения
public TResult GetResult();
// вызывается при завершении
public void OnCompleted(Action continuation);
}
Вот и весь контракт. Компилятор выполняет привязку этих членов по их именам, что имеет два важных следствия:
1. Метод
GetAwaiter может быть методом расширения. Это позволяет сделать тип «ожидаемым», даже если вы не являетесь его владельцем:public static class
TimeSpanAwaitableExtensions
{
public static TaskAwaiter
GetAwaiter(this TimeSpan timeSpan) =>
Task.Delay(timeSpan).GetAwaiter();
}
…
await TimeSpan.FromMilliseconds(200);
2. Объект ожидания может быть структурой. TaskAwaiter, ValueTaskAwaiter и YieldAwaitable.YieldAwaiter являются структурами; поэтому при ожидании уже завершённой задачи не происходит выделения памяти под объект ожидания.
Как используются эти три члена
Для каждого вызова
await компилятор генерирует примерно следующий код:var awaiter = expr.GetAwaiter();
if (!awaiter.IsCompleted)
{
// сохранение состояния
// регистрация продолжения
// возврат к вызывающему методу
awaiter.OnCompleted(MoveNext);
return;
}
var result = awaiter.GetResult();
- Свойство
IsCompleted определяет, будет ли выполнение метода приостановлено. Если оно возвращает true, выполнение продолжается без прерываний, в том же потоке, без планирования задачи и выделения дополнительной памяти. Это «быстрый путь», ради которого и существует ValueTask.- Метод
OnCompleted вызывается только в том случае, если IsCompleted вернуло false. Он получает продолжение и отвечает за его последующий запуск. Где именно будет выполнено это продолжение — решать вам.- Метод
GetResult вызывается в обоих случаях. Он возвращает результат операции и, что важно, именно здесь повторно выбрасываются исключения. Если операция завершилась неудачей, ожидающий объект сообщает об этом, выбрасывая исключение из GetResult, а не через какой-то отдельный канал для передачи ошибок.То, что
GetResult может возвращать void, нормально: именно так ведёт себя await при работе с непараметризованным Task.ICriticalNotifyCompletion
Существует еще один интерфейс —
ICriticalNotifyCompletion, который добавляет метод UnsafeOnCompleted. Практически все реальные реализации ожидающих объектов поддерживают его. Разница заключается в работе с ExecutionContext. Ожидается, что OnCompleted захватит текущий ExecutionContext и восстановит его перед выполнением продолжения. UnsafeOnCompleted этого делать не обязан.Слово «Unsafe» звучит тревожно, но на деле всё не так страшно: создаваемый компилятором конечный автомат уже захватывает
ExecutionContext при запуске метода и восстанавливает его при каждом вызове MoveNext. Повторный захват контекста для каждого await был бы излишней работой. Поэтому конечный автомат вызывает UnsafeOnCompleted, если ожидающий объект его предоставляет, а в противном случае использует «безопасную» версию (OnCompleted).Реализуйте оба интерфейса. В
UnsafeOnCompleted должна выполняться основная логика, а OnCompleted может просто вызывать UnsafeOnCompleted — при условии, что у вас нет собственного контекстного состояния, которое нужно передавать дальше.Полезный объект, поддерживающий ожидание
Task.Yield() возвращает управление в текущий SynchronizationContext (например, в UI-приложении это означает возврат в UI-поток). Иногда же требуется обратное: безусловно покинуть текущий поток и перейти в поток из пула потоков.public readonly struct
ThreadPoolAwaitable : ICriticalNotifyCompletion
{
public ThreadPoolAwaitable GetAwaiter() => this;
public bool IsCompleted => false;
public void GetResult()
{
}
public void OnCompleted(Action continuation)
=> UnsafeOnCompleted(continuation);
public void UnsafeOnCompleted(Action continuation)
=> ThreadPool.UnsafeQueueUserWorkItem(
static state => ((Action)state!)(), continuation);
}
…
await default(ThreadPoolAwaitable);
// дальнейший код выполняется в потоке из пула
Особенности:
- Тип сам является своим собственным «ожидателем».
GetAwaiter() возвращает this. Это распространённый прием, который позволяет избежать лишнего выделения памяти и дополнительного уровня косвенности; YieldAwaitable использует такой же подход.- Свойство
IsCompleted всегда возвращает false. Это сделано намеренно: если бы оно возвращало true, продолжение выполнялось бы синхронно в текущем потоке, а именно этого мы и стараемся избежать.- Это readonly-структура. Для операции ожидания не требуется выделение памяти в куче.
Примечание: Тот же механизм, только наоборот, позволяет в UI-фреймворках использовать конструкцию
await someControl.Dispatcher; для возврата в UI-поток. Метод OnCompleted объекта ожидания в данном случае помещает продолжение в диспетчер, а не в пул потоков.Когда стоит создавать свой объект ожидания
Довольно редко. В большинстве случаев правильным выбором будут
Task, ValueTask или TaskCompletionSource. Создание собственного объекта ожидания оправдано в следующих ситуациях:1. Вы хотите, чтобы
await означал «смену контекста» (как в случае с Task.Yield() или ThreadPoolAwaitable). При этом не нужно передавать какой-либо результат — важен только факт планирования выполнения.2. Вы работаете с механизмом завершения операций, который существенно отличается от стандартного, и хотите избежать выделения памяти под объект
Task для каждой операции. Для этого существует интерфейс IValueTaskSource (про него позже в этой серии).3. Вы хотите сделать существующий тип «ожидаемым», не меняя его исходный код — здесь поможет прием с методами расширения.
Важно помнить: собственный объект ожидания не поддерживает композицию. Он не будет работать с
Task.WhenAll, WaitAsync или любыми другими механизмами, ожидающими объект Task. Если вам нужны такие возможности, просто возвращайте Task.Источник: https://www.meziantou.net/the-awaitable-pattern-writing-your-own-awaitable.htm
👍9
День 2820. #ЗаметкиНаПолях #Async
Ещё Раз Про Async/Await в C#. Часть 5
Как компилятор C# переписывает асинхронный метод. Начало
Вплоть до версии .NET 10,
Метод содержит все элементы, с которыми приходится работать компилятору: параметр, локальные переменные, значения которых должны сохраняться после
Чтобы увидеть, что с методом делает компилятор, можно либо декомпилировать его с помощью ILSpy, либо использовать онлайн инструмент sharplab.io.
Что осталось от исходного метода
По порядку:
1. Создаётся экземпляр конечного автомата.
2. Для него создаётся построитель —
3. Параметры копируются в конечный автомат. Теперь они становятся его полями, поскольку код, который их использует, находится в другом месте.
4. Состояние устанавливается в значение -1 (не запущено, не приостановлено).
5. Вызывается метод
Сгенерированные имена с угловыми скобками не являются допустимыми идентификаторами в языке C#. Это сделано намеренно: они не могут конфликтовать с именами, которые вы задаёте в своём коде, и вы не сможете случайно сослаться на них.
Примечание: Атрибут
Важное следствие: вызов асинхронного метода приводит к выполнению его тела до первого ожидания (
Сгенерированный тип
Ниже представлены поля конечного автомата:
-
-
-
-
-
-
Важно:
1. Локальная переменная становится полем класса только в том случае, если она необходима после оператора
2. Слоты для объектов ожидания привязаны к их типу, а не к конкретному оператору
Окончание следует…
Источник: https://www.meziantou.net/how-the-csharp-compiler-rewrites-an-async-method.htm
Ещё Раз Про Async/Await в C#. Часть 5
Как компилятор C# переписывает асинхронный метод. Начало
Вплоть до версии .NET 10,
async и await не являются встроенными возможностями среды выполнения. Компилятор переписывает код метода с модификатором async. Он извлекает тело метода, переносит его в автоматически созданный тип, а исходный метод заменяет небольшим фрагментом кода, который инициализирует этот тип и запускает его метод. Всё остальное — планирование выполнения, обработка исключений, формирование значения, которое в итоге попадает в возвращаемый объект Task, — представляет собой обычный код, который вы могли бы написать самостоятельно. Сегодня рассмотрим небольшой асинхронный метод и проанализируем, во что его превращает компилятор.Метод содержит все элементы, с которыми приходится работать компилятору: параметр, локальные переменные, значения которых должны сохраняться после
await, два await разных типов, цикл, а также блок try/finally:public static async Task<int>
SumPageSizesAsync(string[] pages)
{
var total = 0;
var sw = Stopwatch.StartNew();
try
{
foreach (var page in pages)
{
var content = await LoadAsync(page);
total += content.Length;
}
await Task.Yield();
return total;
}
finally
{
Console.WriteLine($"Loaded {pages.Length} pages in {sw.ElapsedMilliseconds}ms");
}
}
private static async Task<string>
LoadAsync(string page)
{
await Task.Delay(1);
return page;
}
Чтобы увидеть, что с методом делает компилятор, можно либо декомпилировать его с помощью ILSpy, либо использовать онлайн инструмент sharplab.io.
Что осталось от исходного метода
[AsyncStateMachine(typeof(<SumPageSizesAsync>d__0))]
public static Task<int> SumPageSizesAsync(string[] pages)
{
<SumPageSizesAsync>d__0 stateMachine = default(<SumPageSizesAsync>d__0);
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.pages = pages;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
По порядку:
1. Создаётся экземпляр конечного автомата.
2. Для него создаётся построитель —
AsyncTaskMethodBuilder<int>. Он управляет объектом Task, который будут ожидать вызывающие стороны; именно он отвечает за завершение.3. Параметры копируются в конечный автомат. Теперь они становятся его полями, поскольку код, который их использует, находится в другом месте.
4. Состояние устанавливается в значение -1 (не запущено, не приостановлено).
5. Вызывается метод
Start, который запускает выполнение тела метода, после чего возвращается объект Task.Сгенерированные имена с угловыми скобками не являются допустимыми идентификаторами в языке C#. Это сделано намеренно: они не могут конфликтовать с именами, которые вы задаёте в своём коде, и вы не сможете случайно сослаться на них.
Примечание: Атрибут
[AsyncStateMachine] используют отладчики, профилировщики и анализаторы, чтобы связать метод с типом, который фактически содержит его логику.Важное следствие: вызов асинхронного метода приводит к выполнению его тела до первого ожидания (
await) операции, которая ещё не завершилась. Только после этого управление возвращается вызывающей стороне.Сгенерированный тип
Ниже представлены поля конечного автомата:
private struct <SumPageSizesAsync>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<int> <>t__builder;
public string[] pages;
private int <total>5__2;
private Stopwatch <stopwatch>5__3;
private string[] <>7__wrap3;
private int <>7__wrap4;
private TaskAwaiter<string> <>u__1;
private YieldAwaitable.YieldAwaiter <>u__2;
}
-
<>1__state- указывает точку возобновления выполнения: -1 — выполняется, -2 — завершено; значения 0 и выше соответствуют отдельным операторам await, на которых выполнение может быть приостановлено.-
<>t__builder- объект-построитель, который управляет возвращаемым объектом Task.-
pages - параметр метода; сохраняет своё исходное имя.-
<total>5__2, <sw>5__3 - локальные переменные, которые требуются после выполнения оператора await; отчасти сохраняют свои исходные имена.-
<>7__wrap3, <>7__wrap4 - состояние цикла foreach: массив и текущий индекс. Компилятору они необходимы, чтобы сохранить контекст при выполнении await внутри цикла.-
<>u__1, <>u__2 - по одному слоту на каждый тип объектов ожидания; удерживают их, пока метод находится в приостановленном состоянии.Важно:
1. Локальная переменная становится полем класса только в том случае, если она необходима после оператора
await. Если же переменная используется исключительно в промежутке между двумя операторами await, она остаётся обычной локальной переменной. Поэтому асинхронный метод, сохраняющий большое количество данных, оказывается более «дорогим», чем метод, который этого не делает. Кроме того, перенос части логики в отдельный, синхронный метод позволяет уменьшить размер конечного автомата.2. Слоты для объектов ожидания привязаны к их типу, а не к конкретному оператору
await. Метод LoadAsync возвращает Task<string>, поэтому его «ожидатель» типа TaskAwaiter<string>. А Task.Yield() возвращает YieldAwaitable и его «ожидатель» другого типа. Сколько типов, столько и полей.Окончание следует…
Источник: https://www.meziantou.net/how-the-csharp-compiler-rewrites-an-async-method.htm
👍8
День 2822. #ЗаметкиНаПолях
Таймаут, Который Никогда не Срабатывал
У нас был такой код:
Отправка запроса выполняется с помощью
Выглядит логично. Сможете найти здесь ошибку? Напишите в комментариях, если нашли, не читая дальше.
На самом деле их тут целых две :)
Ошибка 1. Тайм-аут никогда не срабатывает
Конструкция
Небольшой пример для воспроизведения:
Тайм-аут составляет 1 секунду, однако задержка длится все 3 секунды.
Таким образом, в изначальном коде единственным действующим тайм-аутом остается
Ошибка 2. При повторной попытке используется тот же токен
Токен отмены
При выполнении повторной попытки через Polly условие
Из этого также следует, что простого переноса конструкции
Исправление:
Теперь CancellationTokenSource существует на протяжении всей попытки, а каждая попытка получает собственный локальный токен, что исключает перезапись захваченного параметра.
Источник: https://josef.codes/the-timeout-that-never-fired/
Таймаут, Который Никогда не Срабатывал
У нас был такой код:
public async Task<Result<TData>> SendAsync<TData>(
Func<HttpRequestMessage> requestFactory,
RequestOptions? opts = null,
CancellationToken ct = default)
{
opts ??= new RequestOptions();
var policy = opts.Policy ?? DefaultPollyPolicy;
var result = await
policy.ExecuteAndCaptureAsync(async () =>
{
using var request = requestFactory();
if (ct == CancellationToken.None)
{
// Если таймаут не задан, ставим 5 сек
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
ct = cts.Token;
}
using var response =
await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct);
// …
});
// …
}
Отправка запроса выполняется с помощью
HttpClient, который обёрнут в политику Polly (по умолчанию настроенную на одну повторную попытку). Если вызывающий код не передаёт токен отмены, мы создаём свой токен с тайм-аутом 5 секунд.Выглядит логично. Сможете найти здесь ошибку? Напишите в комментариях, если нашли, не читая дальше.
На самом деле их тут целых две :)
Ошибка 1. Тайм-аут никогда не срабатывает
Конструкция
using var освобождает ресурс по завершении области видимости, в которой она объявлена. В данном случае такой областью является блок if, а не лямбда-выражение. В результате CancellationTokenSource уничтожается (вызывается метод Dispose()) в момент закрывающей фигурной скобки — ещё до того, как запрос будет отправлен. При вызове Dispose() на CancellationTokenSource его внутренний таймер также уничтожается. Токен, полученный из этого объекта, формально продолжает «работать» (исключения не возникает), но он никогда не перейдёт в отменённое состояние.Небольшой пример для воспроизведения:
var ct = CancellationToken.None;
var sw = Stopwatch.StartNew();
if (ct == CancellationToken.None)
{
using var cts =
new CancellationTokenSource(TimeSpan.FromSeconds(1));
ct = cts.Token;
}
try
{
await Task.Delay(TimeSpan.FromSeconds(3), ct);
Console.WriteLine($"Завершено спустя
{sw.Elapsed.TotalSeconds:F1}сек");
}
catch (Exception e)
{
Console.WriteLine($"{e.GetType().Name} спустя
{sw.Elapsed.TotalSeconds:F1}сек");
}
// Вывод
Завершено спустя 3.0сек
Тайм-аут составляет 1 секунду, однако задержка длится все 3 секунды.
Таким образом, в изначальном коде единственным действующим тайм-аутом остается
HttpClient.Timeout, значение которого по умолчанию составляет 100 секунд.Ошибка 2. При повторной попытке используется тот же токен
Токен отмены
ct является параметром метода, и лямбда-выражение захватывает его. Следовательно, когда лямбда-выражение присваивает ему значение, меняется не локальная копия, а одна и та же переменная при каждом вызове лямбды.При выполнении повторной попытки через Polly условие
ct == CancellationToken.None перестаёт быть истинным. В результате блок if пропускается, и для повторной попытки используется токен из первой попытки — тот самый, который относится к уже уничтоженному объекту CancellationTokenSource.Из этого также следует, что простого переноса конструкции
using var за пределы блока if недостаточно. Это решило бы проблему для первой попытки, но повторная попытка все равно выполнялась бы без тайм-аута.Исправление:
using var timeoutCts = ct == CancellationToken.None
// таймаут 5 сек
? new CancellationTokenSource(TimeSpan.FromSeconds(5))
: null;
var token = timeoutCts?.Token ?? ct;
using var response =
await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
Теперь CancellationTokenSource существует на протяжении всей попытки, а каждая попытка получает собственный локальный токен, что исключает перезапись захваченного параметра.
Источник: https://josef.codes/the-timeout-that-never-fired/
👍7👎5
День 2823. #СтатьяДня #AltenarCollab
Heartbeat: как мы создали систему управления системой вместо обычных сигналов
Обычно никто не обращает особого внимания на вспомогательные сервисы вроде мониторинга здоровья (HeartBeat). Они обслуживающие, клиент не видит ошибок в них, да и вообще не имеет к ним доступа. Поэтому (если только у команды не было негативного опыта и – больше того – извлечённых из этого опыта правильных уроков), заботятся об их правильной работе или отказоустойчивости вроде как в последнюю очередь. Есть? Хорошо. Что-то там мониторят? Отлично. А правильно мониторят? Могут своевременно сообщить о сбое до того, как вам начнут звонить разгневанные клиенты? Этот ваш HeartBeat, он вообще, вот прямо сейчас, работает?
А что, если система представляет собой высоконагруженный поток данных в реальном времени, неполадки в одном из сервисов обнаруживаются клиентами также в реальном времени, и устранить их желательно не завтра, после утреннего кофе, а также в реальном времени?
Александр Илларионов из Altenar в своей статье на Хабре рассказывает, как они разрабатывали такую систему, которая могла не просто мониторить real-time сервисы, но и своевременно поднимать тревогу и даже предпринимать некоторые действия, вроде отключения сбоящего сервиса.
Статье немного не хватает последовательности, и автор упускает из повествования некоторые детали. Очевидно, он «в контексте» и понимает, что, зачем и почему. Мне этого понимания в некоторых местах не хватало. Например:
При этом ранее не упоминалось, что значит заблокировать трафик и чем это отличается от отключения.
Но в принципе тема довольно интересная. Хотелось бы, чтобы Александр её развил и рассказал поподробнее. Возможно, в серии статей. Какие конкретно сервисы мониторинга и управления были созданы, как распределялись между ними обязанности и, возможно, детали их реализации.
Heartbeat: как мы создали систему управления системой вместо обычных сигналов
Обычно никто не обращает особого внимания на вспомогательные сервисы вроде мониторинга здоровья (HeartBeat). Они обслуживающие, клиент не видит ошибок в них, да и вообще не имеет к ним доступа. Поэтому (если только у команды не было негативного опыта и – больше того – извлечённых из этого опыта правильных уроков), заботятся об их правильной работе или отказоустойчивости вроде как в последнюю очередь. Есть? Хорошо. Что-то там мониторят? Отлично. А правильно мониторят? Могут своевременно сообщить о сбое до того, как вам начнут звонить разгневанные клиенты? Этот ваш HeartBeat, он вообще, вот прямо сейчас, работает?
А что, если система представляет собой высоконагруженный поток данных в реальном времени, неполадки в одном из сервисов обнаруживаются клиентами также в реальном времени, и устранить их желательно не завтра, после утреннего кофе, а также в реальном времени?
Александр Илларионов из Altenar в своей статье на Хабре рассказывает, как они разрабатывали такую систему, которая могла не просто мониторить real-time сервисы, но и своевременно поднимать тревогу и даже предпринимать некоторые действия, вроде отключения сбоящего сервиса.
Статье немного не хватает последовательности, и автор упускает из повествования некоторые детали. Очевидно, он «в контексте» и понимает, что, зачем и почему. Мне этого понимания в некоторых местах не хватало. Например:
Именно поэтому для нас принципиально важна последовательность в этих действиях: сначала заблокировать трафик, а потом уже отключить его и сообщить клиенту о проблеме.
При этом ранее не упоминалось, что значит заблокировать трафик и чем это отличается от отключения.
Но в принципе тема довольно интересная. Хотелось бы, чтобы Александр её развил и рассказал поподробнее. Возможно, в серии статей. Какие конкретно сервисы мониторинга и управления были созданы, как распределялись между ними обязанности и, возможно, детали их реализации.
👍3