tgindex

.NET Разработчик

описание

Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#. Для связи: @SBenzenko Поддержать канал: - https://boosty.to/netdeveloperdiary - https://patreon.com/user?u=52551826 - https://pay.cloudtips.ru/p/70df3b3b

6 726
подписчиков
Охват к подписчикам
18,4%
ERR
Реакции к просмотрам
0,49%
339 на 50 постов
Пересылки к просмотрам
1,04%
721
Постов в день
1,1
всего 356

Где отзываются чаще

доля реакций к просмотрам
  • 4 июл.без подписи1,56%
  • 27 июл.День 2735. #ЗаметкиНаПолях #Cancellation Токен Отмены: Ошибки, Которые Незаметно Приводят к Проблемам. Начало Пользователь закрывает вкладку во время выполнения запроса. Клиент отключается. На панели управления этот запрос должен отображаться как отменённый в течение миллисекунд. Вместо этого он продолжает выполняться ещё секунды — обращается к БД, API, записывает результат, который никто никогда не прочтёт. При тысячах прерванных запросов в день вы платите за вычислительные ресурсы, которые никому не служат. Это чаще всего токен отмены, который корректно создаётся в начале конвейера обработки запросов, а затем незаметно перестаёт передаваться дальше. ASP.NET Core предоставляет возможность отмены практически бесплатно. Токен интегрирован в конвейер, и фреймворк отменяет его в момент отключения клиента. Все ошибки происходят в коде, который забывает о существовании токена. Каждый HTTP-запрос в ASP.NET Core содержит токен, который вы можете запросить напрямую: app.MapGet("/reports/{id}", async (int id, HttpContext http, ReportService service) => { // Этот токен отменяется при отключении клиента // или на старте процедуры завершения работы сервера CancellationToken ct = http.RequestAborted; var report = await service.BuildReportAsync(id, ct); return Results.Ok(report); }); Редко требуется явно использовать HttpContext.RequestAborted — обработчики в минимальных API и методы-действия в MVC могут просто принимать параметр CancellationToken, и фреймворк автоматически привязывает RequestAborted к нему: app.MapGet("/reports/{id}", async (int id, ReportService service, CancellationToken ct) => { var report = await service.BuildReportAsync(id, ct); return Results.Ok(report); }); Вот и весь контракт: токен передаётся вам, а ваша задача — передать его каждому ожидаемому вызову ниже. Описанные ниже ошибки — это варианты нарушения этой цепочки. Ошибка 1: «Проглатывание» токена на уровне сервиса Наиболее распространённое нарушение - сигнатура метода сервиса игнорирует параметр: public class ReportService { // … public async Task<Report> BuildReportAsync(int id) { var order = await _db.Orders.FindAsync(id); var pricing = await _prcClient.GetAsync($"/price/{id}"); return Map(order, pricing); } } Здесь ничего не вызывает ошибок. На код-ревью всё выглядит нормально, если только вы специально это не проверяете. Но с этого момента запрос становится неотменяемым — клиент может исчезнуть, a запрос к БД и HTTP-вызов всё равно завершатся. Верный вариант: public async Task<Report> BuildReportAsync( int id, CancellationToken ct) { var order = await _db.Orders.FindAsync(new object[] { id }, ct); var pricing = await _prcClient.GetAsync($"/price/{id}", ct); return Map(order, pricing); } Правило: если метод ожидает чего-либо, он обязан принимать CancellationToken. Анализатор, вроде CA2016 (часть встроенных анализаторов .NET), отметит большинство таких случаев — включите его как предупреждение, а не просто как рекомендацию. Ошибка №2: Task.Run без токена или с неправильным токеном Task.Run имеет две перегрузки, и легко вызвать ту, которая делает вашу работу неотменяемой: public async Task<byte[]> GenerateExportAsync( int reportId, CancellationToken ct) { return await Task.Run(() => BuildReport(reportId), ct); } Передача ct в Task.Run останавливает запуск задачи только в том случае, если она уже отменена, но ничего не делает, если делегат уже запущен, т.к. BuildReport не может отслеживать отмену. Решение – передавать токен в исполняемый метод, а не только в Task.Run: public async Task<byte[]> GenerateExportAsync( int reportId, CancellationToken ct) { return await Task.Run(() => BuildReport(reportId, ct), ct); } private byte[] BuildReport( int id, CancellationToken ct) { for (int page = 0; page < TotalPages(id); page++) { // периодически проверяем отмену ct.ThrowIfCancellationRequested(); RenderPage(id, page); } … } Для циклов, интенсивно использующих процессор, вызывайте ct.ThrowIfCancellationRequested() между итерациями, а не только один раз в начале. Цикл, который проверяет токен один раз, а затем работает десять секунд, не может быть корректно отменён. Продолжение следует… Источник: https://thecodeman.net/posts/cancellation-tokens-in-aspnet-core-mistakes1,38%
  • 8 авг.День 2747. #TipsAndTricks #VisualStudio Загадочный Флажок .dev.localhost При создании нового проекта ASP.NET Core в Visual Studio есть флажок, который легко пропустить: Use the .dev.localhost TLD in the application URL (Использовать домен верхнего уровня .dev.localhost в URL приложения). Давайте разберёмся, что он делает. Проблема Когда вы работаете над несколькими локальными веб-проектами, все они располагаются по одному и тому же адресу localhost. Различить их можно только по номеру порта. Если у вас в работе 3 проекта, то в браузере может быть localhost:5001, localhost:5215, localhost:7099 — и вы не будете знать, какой из них какой, пока не посмотрите на страницу. Есть вторая, менее заметная проблема: поскольку все используют имя localhost, файлы cookie и другие хранилища браузера, привязанные к домену, также используются всеми вашими локальными приложениями. Это обычно нежелательно при тестировании. Что такое .dev.localhost? .localhost — это зарезервированный домен верхнего уровня, определённый в RFC2606 и RFC6761 специально для локального тестирования. Современные браузеры уже разрешают всё, что заканчивается на .localhost, напрямую в локальный адрес (127.0.0.1/::1), поэтому myapp.localhost ведет себя точно так же, как localhost, если только вы не отредактировали файл .hosts или настройки DNS. Начиная с .NET 10, ASP.NET Core развивает эту идею и добавляет полноценную поддержку поддомена .dev.localhost. Шаблоны проектов для ASP.NET Core Empty и Blazor Web App могут объединить имя вашего проекта с этим суффиксом, поэтому вместо: https://localhost:7099 вы получите: https://myapp.dev.localhost:7099 Для этого и флажок. Настройка также доступна из командной строки, если вы не используете мастер Visual Studio: dotnet new web -n MyApp --localhost-tld Примечание: Kestrel распознаёт адреса .localhost. Когда ваш профиль запуска или ASPNETCORE_URLS указывает на имя .dev.localhost, Kestrel привязывается только к локальному адресу (127.0.0.1/::1), а не ко всем интерфейсам. Он также регистрирует как адрес .localhost, так и обычный localhost при запуске, поэтому оба варианта по-прежнему работают. Зачем? - Вы сможете различать свои приложения. Название проекта отображается прямо в адресной строке, а не в номере порта, который вам нужно запоминать. - Никаких конфликтов кук и хранилищ. Поскольку каждое приложение получает свой собственный поддомен, хранилище браузера, привязанное к домену, больше не является общим для всех ваших локальных проектов. - HTTPS по-прежнему работает «из коробки». Сертификат разработчика ASP.NET Core уже указывает *.dev.localhost в качестве альтернативного имени субъекта. Вам не нужно ничего дополнительно генерировать — dotnet dev-certs https уже это обеспечивает. Сертификат с подстановочным знаком для *.localhost сам по себе недействителен для домена верхнего уровня, именно поэтому существует поддомен .dev. - Ничего не сломается. Kestrel продолжает прослушивать обычный localhost, поэтому инструменты или скрипты, которые обращаются к localhost:7099, продолжат работать. Единственная загвоздка: Safari Safari на macOS не разрешает имена *.localhost автоматически. Если вы тестируете в Safari, используйте обычный адрес localhost. То же самое относится и к клиентам, не являющимся браузерами. Некоторые HTTP-клиенты и инструменты разрешают имена .localhost через обычный стек DNS вместо обработки их в особом порядке, и, если ваш DNS не знает, что с этим делать, запрос просто завершится неудачей. В таких случаях продолжайте использовать обычный localhost. Источник: https://bartwullems.blogspot.com/2026/07/the-mysterious-devlocalhost-checkbox.html1,34%
  • 9 июл.без подписи1,06%
  • 29 июн.без подписи0,97%
  • 10 авг.День 2749. #BestPractices #SQL Как Оптимизировать SQL-запросы. Часть 1 Медленный SQL-запрос — один из самых простых способов испортить быстрое приложение. У вас может быть чистая архитектура, отличный кэш и мощный сервер — и всё равно страница может зависать из-за того, что один запрос сканирует миллион строк без индекса. Большинство успехов достигается за счёт одного и того же небольшого набора методов, применяемых снова и снова. Некоторые из них очевидны. Некоторые противоречат советам, которые вы, вероятно, уже слышали. Советы можно условно разделить на 6 групп. Замечание: здесь мы рассматриваем PostgreSQL. Те же принципы применимы и к другим БД, хотя точный синтаксис может отличаться. Группа I. Написание запросов, удобных для индексации Индекс полезен только в том случае, если ваш запрос позволяет БД его использовать. 1. Разумно используйте индексы Индексы — самый мощный метод повышения производительности чтения. Это отсортированная структура данных, которая позволяет БД находить строки, не сканируя всю таблицу, подобно тому, как оглавление книги избавляет вас от необходимости пролистывать все страницы. Создавайте индексы по столбцам, по которым вы чаще всего выполняете фильтрацию, соединение, сортировку и группировку — столбцам в WHERE, JOIN, ORDER BY и GROUP BY. Когда используется несколько столбцов одновременно, один составной индекс, охватывающий их, намного лучше, чем отдельные индексы по одному столбцу: -- Составной индекс для частой фильтрации по статусу и дате CREATE INDEX idx_orders_status_order_date ON orders (status, order_date); Этот индекс ускоряет запросы, фильтрующие по статусу, а также по статусу и дате заказа. Порядок столбцов имеет значение: индекс по (status, order_date) помогает запросам, которые сначала фильтруют по статусу, но не запросам, которые фильтруют только по дате заказа. Вы также можете создать покрывающий индекс, который хранит дополнительные значения столбцов внутри индекса. Это полезно для небольших частых запросов на поиск, когда запросу нужны только столбцы, доступные в индексе, чтобы БД могла избежать чтения фактических строк таблицы: -- Добавляем часто читаемые данные CREATE INDEX idx_orders_status_order_date_covering ON orders (status, order_date) INCLUDE (customer_id, total_amount); SELECT customer_id, total_amount FROM orders WHERE status = 'paid' AND order_date >= DATE '2026-01-01'; Замечание: индексы не бесплатны. Каждый индекс необходимо обновлять при каждой вставке, обновлении и удалении, и это занимает место на диске. Индексируйте столбцы, которые фактически используются вашими запросами, а не каждый столбец. 2. Избегайте функций в WHERE Обёртывание столбца в функцию — один из наиболее распространённых способов случайно отключить индекс. Когда вы вызываете функцию для столбца, БД должна вычислить значение функции для каждой строки, прежде чем сможет сравнить его, поэтому она не может использовать индекс для исходного столбца: -- Плохо: функция по order_date отключает сканирование по индексу SELECT * FROM orders WHERE EXTRACT(YEAR FROM order_date) = 2025; Перепишите условие так, чтобы оно сравнивало исходный столбец с диапазоном: -- Хорошо: диапазон по чистому значению столбца SELECT * FROM orders WHERE order_date >= '2025-01-01' AND order_date < '2026-01-01'; Оба запроса возвращают одни и те же строки, но только второй может использовать индекс по order_date. То же правило применяется к LOWER(email), CAST(…) и арифметическим операциям по столбцу. Если вам часто нужно фильтровать по вычисляемому значению, создайте вместо этого функциональный индекс для этого конкретного выражения. 3. Избегайте символов подстановки в начале запроса LIKE Шаблон LIKE, начинающийся с символа подстановки, не может использовать обычный индекс. БД считывает индекс слева направо, поэтому ей необходимо знать начало значения. Шаблон типа '%son' скрывает начало и заставляет выполнять полное сканирование: -- Плохо: полное сканирование таблицы SELECT * FROM customers WHERE last_name LIKE '%son'; -- Хорошо: сканирование индекса по известному префиксу SELECT * FROM customers WHERE last_name LIKE 'Anders%'; Если вам действительно нужно выполнить contains-поиск в тексте, используйте полнотекстовый поиск или триграммный индекс (расширение pg_trgm в PostgreSQL), созданный специально для этой задачи. 4. Точное соответствие типов данных Сравнение двух разных типов данных заставляет БД преобразовывать один из них, и это преобразование может незаметно отключить индекс. Если столбец является целым числом, но вы сравниваете его со строкой, или соединяете int-ключ с bigint-ключом, БД добавляет неявное приведение типов — и индекс по исходному столбцу может быть пропущен. Сохраняйте одинаковые типы с обеих сторон каждого соединения и фильтрации: CREATE TABLE logs ( log_id int PRIMARY KEY, event_date timestamptz NOT NULL, user_id int NOT NULL ); Определите logs.user_id так, чтобы он соответствовал типу users.id, и тогда соединение будет использовать индекс с обеих сторон. Продолжение следует… https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices0,95%
  • 6 авг.День 2745. #ЗаметкиНаПолях #Architecture 5 Архитектурных Ошибок, Которые Затрудняют Изменение Системы. Начало Вы можете следовать любым лучшим практикам, использовать чистую архитектуру, event-sourcing, микросервисы или что-либо ещё популярное. В итоге вы всё равно оказываетесь в той же ситуации. У вас система, которую очень сложно изменить. Когда вы всё-таки вносите изменения, вы боитесь что-нибудь сломать. В конце концов всё чаще возникает мысль: «Лучше бы это переписать». Кодовая база может даже выглядеть не так уж плохо. Она может быть организованной и относительно простой для понимания. Но почему-то очень трудно что-то изменить. Причина, вероятно, кроется в одной из 5 архитектурных ошибок. В каждом случае вы принимаете дорогостоящее решение, прежде чем по-настоящему поймёте бизнес или проблемы, которые пытаетесь решить. Вопрос не в том, какую архитектуру использовать? Вопрос должен звучать: «Что мы понимаем о проблеме, что оправдывает архитектурное решение, которое мы собираемся принять?» 1. Выбор архитектуры до понимания предметной области Если в начале обсуждения проекта речь идёт о необходимости микросервисов, событийного моделирования, CQRS, Kafka и Kubernetes, но никто не может объяснить бизнес-процессы или рабочие процессы, вы делаете всё наоборот. Может кто-нибудь объяснить ограничения? Какие проблемы с согласованностью могут возникнуть? Какие возможные режимы отказов? Какие части системы часто меняются? Где допустимы задержки? Архитектурные шаблоны и инструменты сопряжены с компромиссами и дополнительной сложностью: - Нужна независимая развёртываемость? Теперь у вас распределённые операции. - Масштабировать части системы независимо? Придётся бороться со сбоями в сети. - Автономия команды? Потребуется межсервисное и межкомандное взаимодействие. - Изоляция между границами? Получите проблемы согласованности между этими границами. Всегда есть компромисс. У каждого решения есть своя цена. Основное внимание следует уделить факторам, влияющим на вашу систему, и проблемам, которые вы пытаетесь решить. Есть ли проблемы с согласованностью? Есть ли в системе часть, где правила быстро меняются и которую следует изолировать? Содержит ли рабочий процесс задержки, которые необходимо учитывать в последующих процессах? Если вы сначала принимаете технические решения, вы лишь предполагаете, что когда-нибудь столкнётесь с проблемой, которую эти решения должны решить. 2. Создание сервисов сущностей, управляемых CRUD-операциями При этом система полностью управляется своей моделью данных, а не поведением. Код может выглядеть отлично, быть хорошо организован. Но сущности на самом деле представляют собой просто таблицы или графы объектов, отображающие клиентов, заказы, товары и т.п. Вот простой заказ: public class Order { public Guid Id { get; set; } public Guid CustomerId { get; set; } public string Status { get; set; } public decimal Total { get; set; } } Что нам говорит эта модель? Что какие-то данные существуют. Она ничего не говорит нам о том, как ведёт себя заказ. Можно ли его отменить? Возможно да, но как? Просто изменить статус? Когда заказ может быть отправлен? Должен ли он находиться в определённом статусе? Можно ли изменить статус после того, как товар зарезервирован? Модель предоставляет нам только данные. Вся бизнес-логика при этом обычно разбросана повсюду: в контроллерах, обработчиках сообщений, хранимых процедурах или даже во фронтэнде. Сами по себе анемичные модель не плохи. В системе всегда есть части, которые ими по сути являются. Проблема в том, когда всё становится анемичными моделями, а разработка сводится к операциям над обновляемой записью: UpdateOrder(order); // или CancelOrder(order, reason); В UpdateOrder вы просто изменяете свойства заказа. В CancelOrder вы явно сообщаете о намерении отменить заказ и указываете причину. Это выражает поведение и бизнес-намерение. Вопрос, который вы должны задать себе: «Сообщает ли выполняемая мной операция бизнес-намерение?» В этом примере цель в отмене заказа. Как только эта цель становится чётко выраженной, можно начать задавать полезные вопросы: - Можно ли отменить заказ, учитывая, как давно он был размещён? - Был ли уже зарезервирован товар на складе? - Был ли заказ отправлен? - Нужен ли клиенту возврат средств? Эти вопросы вытекают из цели операции. Когда всё рассматривается как обновление, эта цель теряется. Также теряется естественное место, где должны существовать бизнес-правила, проверка и решения по рабочим процессам. Окончание следует… Источник: https://codeopinion.com/5-software-architecture-mistakes/0,77%
  • 24 июл.День 2732. #ЗаметкиНаПолях Решаем Проблему Паники в Кэше В приложении ASP.NET Core API кэшировался довольно ресурсоёмкий отчёт на 60 секунд. При обычной нагрузке всё было нормально: один запрос перестраивал кэш, все остальные читали из него. При пиковой нагрузке десятки запросов поступали одновременно, все видели промах кэша и параллельно выполняли один и тот же ресурсоёмкий запрос. Слой кэширования не помогал. Это называется «паникой в кэше» (cache stampede). Почему это происходит IMemoryCache.GetOrCreate (и его асинхронный аналог) сами по себе не добавляют блокировок: public async Task<Report> GetReportAsync(string key) { if (_cache.TryGetValue(key, out Report cached)) return cached; var report = await _service.BuildReportAsync(key); _cache.Set(key, report, TimeSpan.FromSeconds(60)); return report; } Если 10 запросов придут одновременно после истечения срока действия кэша, для всех TryGetValue вернёт false, и все они вызовут BuildReportAsync. Это не специфично для IMemoryCache. Распределённые кэши, такие как Redis, имеют ту же проблему. Сам кэш не знает и не заботится о том, что параллельные вызывающие процессы собираются запросить тот же ключ. Решение 1: блокировка для каждого ключа с помощью SemaphoreSlim Самое простое решение — заставить одновременные вызывающие процессы для одного и того же ключа ждать завершения первого: private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new(); public async Task<Report> GetReportAsync(string key) { if (_cache.TryGetValue(key, out Report cached)) return cached; var keyLock = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1)); await keyLock.WaitAsync(); try { // Перепроверка: другой запрос мог уже // задать значение, пока мы ждали семафора if (_cache.TryGetValue(key, out cached)) return cached; var report = await _service.BuildReportAsync(key); _cache.Set(key, report, TimeSpan.FromSeconds(60)); return report; } finally { keyLock.Release(); } } Примечание: ConcurrentDictionary<string, SemaphoreSlim> будет бесконечно расти, если его не чистить. Для небольшого набора ключей это нормально. Иначе - удаляйте неиспользуемые семафоры. Решение 2: HybridCache сделает это за вас Microsoft.Extensions.Caching.Hybrid.HybridCache координирует одновременные вызовы для одного и того же ключа, так что одновременно выполняется только один вызов: // регистрация services.AddHybridCache(options => { opts.DefaultEntryOptions = new HybridCacheEntryOptions { Expiration = TimeSpan.FromSeconds(60), LocalCacheExpiration = TimeSpan.FromSeconds(60) }; }); // использование public async Task<Report> GetReportAsync(string key) { return await _cache.GetOrCreateAsync( key, async ct => await _service.BuildReportAsync(key, ct), cancellationToken: default); } HybridCache также предоставляет двухуровневый кэш. Защита от «паники» применяется на локальном уровне на каждом узле; она не предотвращает одновременную перестройку одного и того же ключа двумя разными узлами в кластере, поскольку отсутствует межмашинная блокировка. Для этого потребуется распределённая блокировка или придётся смириться с периодическим двойным перестроением на разных узлах как с менее масштабной версией той же проблемы. Решение 3: не допускать одновременного истечения срока действия всего кэша Даже при блокировке по ключу, проблема может возникать, если срок действия множества разных ключей истекает одновременно. Решение - добавить разброс (jitter): var jitter = TimeSpan.FromSeconds(Random.Shared.Next(0, 10)); _cache.Set(key, report, TimeSpan.FromSeconds(60) + jitter); Источник: https://steven-giesel.com/blogPost/605274d2-719b-4b1f-b0d5-3ad9001d9b56/adding-static-getter-thanks-to-extensions0,66%
  • 14 июл.без подписи0,66%
  • 28 июл.День 2736. #ЗаметкиНаПолях #Cancellation Токен Отмены: Ошибки, Которые Незаметно Приводят к Проблемам. Продолжение Начало Ошибка №3: Игнорирование OperationCanceledException Отмена в .NET работает путём генерации исключений. Когда срабатывает токен, следующая ожидаемая операция генерирует исключение OperationCanceledException (или его подкласс TaskCanceledException). Блок catch, перехватывающий все исключения, перехватит и это — и теперь ваши логи полны «сбоев», которые на самом деле являются просто закрытием вкладок пользователями: try { await service.BuildReportAsync(id, ct); } catch (Exception ex) { _logger.LogError(ex, "Генерация отчёта не удалась"); throw; } Решение – ожидать отмену: try { await service.BuildReportAsync(id, ct); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { _logger.LogDebug("Генерация отчёта №{Id} отменена клиентом", id); throw; // позволяем ASP.NET Core перехватить это и вернуть правильный ответ } catch (Exception ex) { _logger.LogError(ex, "Генерация отчёта не удалась"); throw; } ASP.NET Core умеет обрабатывать необработанное OperationCanceledException, связанное с RequestAborted — оно завершает запрос без записи кода 500. Ошибка №4: Предположение, что фоновый сервис получает токен запроса Эта ошибка сбивает с толку, потому что выглядит как противоположная проблема. У фонового сервиса есть собственный токен отмены, и он не имеет отношения к HTTP-запросу. Он срабатывает только при завершении работы хоста: public class OutboxProcessor : BackgroundService { private readonly IServiceProvider _services; public OutboxProcessor(IServiceProvider services) => _services = services; protected override async Task ExecuteAsync( CancellationToken stopToken) { while (!stopToken.IsCancellationRequested) { using var scope = _services.CreateScope(); var db = scope.ServiceProvider .GetRequiredService<AppDbContext>(); // stopToken здесь означает «приложение закрывается», // а не «эта часть работы отменена кем-то» await ProcessAsync(db, stopToken); await Task.Delay(TimeSpan.FromSeconds(5), stopToken); } } } Если вы запускаете фоновые процессы внутри обработчика запросов и передаёте в него RequestAborted, вы создаёте ошибку: фоновые процессы прекращаются в тот же момент, когда отправляется HTTP-ответ (потому что RequestAborted также срабатывает в этих случаях при некоторых конфигурациях хоста) или когда клиент отключается. Фоновым процессам, которые должны продолжаться после завершения запроса, нужен собственный токен — обычно IHostApplicationLifetime.ApplicationStopping, а не токен запроса: app.MapPost("/reports/{id}/export", async ( int id, ReportService svc, IHostApplicationLifetime lt) => { // НЕ RequestAborted – сервис должен продолжить // работу, даже когда ответ клиенту отправлен _ = svc.GenerateAsync(id, lt.ApplicationStopping); return Results.Accepted(); }); Важное различие: RequestAborted означает «тот, кто вызывал этот запрос, отключился». ApplicationStopping означает «процесс завершается». Использование одного значения вместо другого либо приводит к преждевременной отмене работы, либо к лишней работе, которая должна была быть отменена вместе с запросом. Окончание следует… Источник: https://thecodeman.net/posts/cancellation-tokens-in-aspnet-core-mistakes0,65%
  • 30 июл.День 2738. #ЗаметкиНаПолях Отладка Дампов в Visual Studio Дамп памяти — это файл, содержащий копию памяти конкретного процесса. Файлы дампов в Windows обычно имеют расширение .dmp. Это полезно, когда у вас есть ошибка, которая воспроизводятся только в рабочей среде. И да, это, как правило, крайняя мера! Обычно ошибки обнаруживаются путём воспроизведения проблемы локально, а затем вы можете просто запустить код в отладчике. Но если вам не удаётся найти/воспроизвести ошибку за разумное время, создание дампа — отличный способ получить дополнительную информацию. Создание дампа Лучший инструмент для этого — ProcDump: можно создавать дампы по запросу или по триггеру мониторинга (сбой процесса, высокая загрузка ЦП/памяти и т.д.). Однако ProcDump требует доступа к машине, поэтому он отлично подходит только для локальных или виртуальных развёртываний. Если вы работаете с веб-приложением Azure, вы можете создавать дампы памяти и там. Загрузка дампа Вы можете просто перетащить файл dbg в Visual Studio. Но, прежде чем это сделать, вот пара советов по настройке VS для наилучшего результата: 1. Just my code Снимите флажок Just my code (Только мой код). Удобнее видеть полную картину при отладке файла дампа. 2. Символы Включите загрузку символов (например, файлов .pdb). VS использует символы для расшифровки скомпилированного кода, особенно если он был скомпилирован в режиме Release. Символы часто необходимы для понимания стека. Лучший вариант — указать VS загружать все символы при загрузке файла дампа. Обычно настройку лучше выключать, т.к. загрузка этих символов занимает много времени. Но для при отладке дампа она пригодится. Также нужно указать, откуда загружать файлы символов. Два основных источника — это Microsoft и NuGet (symbols.nuget.org). Microsoft предоставляет символы для своей ОС (как минимум); сервер символов NuGet — это общее место для символов библиотек с открытым кодом. Наконец, хорошо бы настроить локальный кэш символов. Это локальная папка, которая будет хранить все эти файлы pdb. 3. Исходные файлы Включите поддержку сервера исходных файлов. Это способ для Visual Studio загружать точную версию исходных файлов, фактически использованных для компиляции программы. В настоящее время серверы исходных файлов в основном используются для неуправляемого кода, хотя некоторые устаревшие библиотеки .NET могут их использовать. Также лучше включить диспетчер учётных данных Git для Source Link. Source Link — это современная замена серверам исходного кода, по крайней мере, в контексте .NET. Это позволит получать исходные файлы из вашего репозитория исходного кода. Теперь VS готова загрузить файл дампа; просто перетащите его прямо в VS. Затем сходите за чашкой кофе; загрузка всех этих символов в первый раз потребует некоторое время! По мере заполнения кэша файлы дампов будут загружаться быстрее; первая загрузка обычно самая медленная. VS спросит, какой тип отладчика вы хотите запустить; лучше выбрать Mixed (смешанный) — если вы не знаете, в чём проблема, лучше иметь возможность видеть всё. Затем вы перейдёте в режим, похожий на отладку. Конечно, вы не сможете снять паузу или пошагово отладить, но сможете покопаться в настройках. Как правило, окна отладки «Параллельные стеки», «Потоки», «Стек вызовов» и «Модули» являются хорошими отправными точками для попытки разобраться в происходящем. Источник: https://blog.stephencleary.com/2025/12/debug-dumps-in-visual-studio.html0,61%
  • 4 авг.День 2743. #ЗаметкиНаПолях #Microservices Стоит ли Делить Это на Микросервисы? Одни команды внедряют микросервисы, другие отказываются от них. Почти во всех случаях отказа решение о разделении принималось раньше, чем находились причины. Приложение может когда-нибудь масштабироваться. Монолит кажется всё более запутанным с каждым спринтом. В докладе на конференции сказали, что независимые развёртывания — это легко. Ничто из этого не является причиной для перехода на распределённую систему. Между тем, реальная цена высока: микросервисы обменивают локальную сложность на распределённую. Вызов метода становится сетевым. Транзакция становится сагой. Трассировка стека - распределённой трассировкой по трём сервисам и очереди. Иногда этот обмен стоит того. Ответьте на вопросы ниже, и решение делить или нет станет очевидным. 1. Действительно ли у разных частей системы разные потребности в масштабировании? Не потребности, которые могут возникнуть когда-нибудь, а те, которые можно измерить сегодня: одна часть системы обрабатывает в 100 раз больший трафик или требует графического процессора, или потребляет память так, что приходится рассчитывать масштаб всего развёртывания под пиковые нагрузки этой части. Это реальная причина. Выделение «горячего» пути, позволяющего масштабироваться (и падать) независимо, — один из лучших аргументов в пользу выделения сервиса. Но сначала проверьте: большинство монолитов в .NET хорошо масштабируются за балансировщиком нагрузки. Если всё приложение комфортно работает на трёх экземплярах, у вас нет проблемы масштабирования, ради которой стоило бы выделять сервисы. 2. Действительно ли команды мешают друг другу? Несколько команд, одна кодовая база и процесс релиза, где наполовину готовая функция команды А задерживает выпуск исправления командой Б. Развёртывания усложняются, релизы откладываются, постоянные конфликты слияния и т.п. Если это ваша реальность, то независимое развёртывание имеет реальную ценность. Если у вас команда из 6 человек, то нет. Одна команда не настолько сильно мешает сама себе, чтобы оправдать эксплуатацию распределённой системы. Если у вас меньше двух полных команд, организационная польза от микросервисов равна нулю. 3. Можно ли разграничить данные? Каждый сервис должен полностью владеть своими данными. Владение базой данных означает, что ни один другой сервис не будет напрямую считывать её таблицы, даже для одного удобного join'а. Если двум потенциальным сервисам постоянно требуются данные друг друга для ответа на базовые запросы, это не два сервиса, а один который вы собираетесь разорвать пополам. Граница, которая выглядит чистой на организационной диаграмме, может быть безнадёжно запутана на уровне данных. Запутанность не исчезнет, когда вы добавите сеть между половинами. Она усугубится, потому что теперь каждое «соединение» — это вызов API, и сохранение целостности границ данных становится распределённой проблемой. 4. Требует ли что-либо независимого отказа или выпуска? Некоторые части системы предъявляют требования, которых нет у остальных: - Платёжный поток, который должен оставаться работоспособным даже при сбое модуля отчётности; - Компонент, который обязан соответствовать государственным стандартам, и необходимо максимально уменьшить площадь проверяемого кода; - Интеграция, которая выпускается еженедельно, в то время как ядро — ежеквартально; и т.п. Это законные требования к изоляции, и выделение сервиса — это чёткий способ их выразить. Обратите внимание, насколько они конкретны. Обычное желание изолировать сервис в этом списке отсутствует. 5. Можете ли вы позволить себе «налог на платформу»? Прежде чем первый микросервис начнёт приносить какую-либо пользу, вам необходимы: платформа контейнеров, CI/CD для каждого сервиса, централизованное логирование, распределённая трассировка, брокер сообщений и паттерны надёжности, обеспечивающие безопасное взаимодействие между сервисами («Исходящие», «Идемпотентные потребители», «Повторные попытки»). Это вступительный взнос, оплачиваемый в инженерных человеко-месяцах, ещё до получения первого преимущества. Команда, которая не может выделить такие ресурсы, не получит более дешёвую версию в виде микросервисов. Она получит распределённый монолит без всех преимуществ и со всеми его издержками. Оценка - 4 или 5 ответов «да»: разделите проект и начните с одного сервиса. Выделите часть с наиболее чёткими границами и запустите её в продакшене на квартал, прежде чем выделять следующую. - 2 или 3: вам нужны модули, а не сервисы. Модульный монолит даст границы, командное владение кодом и возможность разделения позже без платы за платформу. Границы, которые вы устанавливаете сейчас, — это именно то, что делает последующую миграцию механической, а не героической. - 0 или 1: сохраните монолит и вложите сэкономленную энергию в его совершенствование. Команды, которые пожалели о переходе на микросервисы, почти никогда не ошибались в выборе технологии. Они ошиблись полтора года назад на совещании, где было принято решение о разделении, ещё до того, как стали известны причины. Проведите опрос по пяти вопросам перед подобным совещанием у вас. Источник: https://milanjovanovic.tech/blog/should-you-split-that-into-microservices-ask-these-5-questions-first0,61%