C# Ready | Unity
10.6K subscribers
1.52K photos
85 videos
757 links
Авторский канал по разработке на C# и Unity.
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!

Cотрудничество: @energy_c

Реклама на бирже: https://telega.in/channels/csharp_ready/card

РКН: https://clck.ru/3SBaT3
Download Telegram
Обрабатываем список запросов параллельно с ограничением!

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

Сначала задаём лимит параллелизма и общий токен отмены:
var options = new ParallelOptions
{
    MaxDegreeOfParallelism = 4,
    CancellationToken = cancellationToken
};


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

Передаём каждую запись в асинхронную операцию и не блокируем поток через Wait или Result:
await Parallel.ForEachAsync(records, options, async (record, token) =>
{
    await client.PostAsJsonAsync("/events", record, token);
});


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

Соберём результат ответа и явно обработаем неуспешный HTTP-статус:
await Parallel.ForEachAsync(records, options, async (record, token) =>
{
    using var response = await client.PostAsJsonAsync(
        "/events", record, token);
    response.EnsureSuccessStatusCode();
});


Освобождение ответа важно при массовой обработке. Если один запрос вернул ошибку, исключение выйдет из цикла, поэтому вызывающему коду нужно решить, завершать всю пачку, повторять отдельную запись или собирать ошибки поэлементно.

Запусти обработку внутри обычного try и отличай отмену от сбоя сервера:
try
{
    await Parallel.ForEachAsync(records, options, ProcessAsync);
}


Ожидаемую отмену обработай отдельно от ошибок сервера:
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
    logger.LogInformation("Обработка отменена");
}


Сетевые ошибки можно записать отдельно или передать вызывающему уровню:
catch (HttpRequestException exception)
{
    logger.LogError(exception, "Не удалось отправить запись");
}


Parallel.ForEachAsync подходит для независимых элементов, когда важен ограниченный параллелизм. Лимит не задаёт частоту запросов в секунду, не сохраняет порядок завершения и сам по себе не реализует повторы.

➡️ C# Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3🔥3
Проверяем заголовок PNG и читаем размеры!

Нужно быстро узнать размеры изображения до декодирования всего файла. В этой задаче читаем фиксированные 24 байта, проверяем сигнатуру PNG и первый чанк IHDR, затем получаем ширину и высоту.

В решении:
• читаем заголовок без загрузки изображения целиком
• отклоняем усечённый или неверный файл
• декодируем размеры с учётом порядка байтов


Такой код подходит для предварительной проверки загрузок и очередей обработки изображений.

➡️ C# Ready | #задача
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤4🔥4
🔥12❤3👍3😁1
Шпаргалка по сериализации JSON в C#!

Source generation создаёт код сериализации во время сборки и помогает проектам с trimming и Native AOT. В контекст добавляются используемые модели, а вызовы получают сгенерированный JsonTypeInfo без поиска метаданных через reflection.

➡️ C# Ready | #шпора
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7🔥5👍3🤝2
Знали, почему долгоживущий HttpClient может обращаться по устаревшему DNS-адресу?

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

Создаём общий клиент и задаём срок жизни соединений:
static readonly HttpClient Client = new(
    new SocketsHttpHandler
    {
        PooledConnectionLifetime =
            TimeSpan.FromMinutes(5)
    });


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

Клиент повторно используем между запросами:
using var response =
    await Client.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();


Создание нового HttpClient на каждый запрос может постоянно плодить пулы соединений и привести к исчерпанию сокетов. Для приложений с DI вместо статического клиента подходит IHttpClientFactory с настройкой handler lifetime.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍3🔥3
Как индексация строк изменила систему типов HydraScript!

Автор добавил длину и обращение по индексу в интерпретируемый язык на C#. Работа над строками показала, что правила операций удобнее хранить рядом с типами, а не разносить по семантическому анализатору.

В статье разбирают:
• как интерфейс IOperator описывает допустимость операции и тип результата
• почему строки и массивы могут использовать общие правила индексации
• где проходит граница между проверкой типов и выполнением выражения

Такой подход упрощает развитие анализатора, но оставляет диагностику ошибок и контекст исходного кода в соответствующем слое.

Продолжай читать на Habr!


➡️ C# Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥4👍3
Запускаем периодическую проверку без наложения запросов!

Обычный таймер может вызвать следующий callback, пока предыдущая проверка ещё ждёт сеть. Сегодня соберём последовательный polling через PeriodicTimer и передадим отмену внутрь операции.

Создаём таймер с нужным интервалом:
using var timer = new PeriodicTimer(
    TimeSpan.FromSeconds(15));


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

Ждём тик и завершаем цикл через cancellation token:
while (await timer.WaitForNextTickAsync(token))
{
    await RefreshAsync(token);
}


Следующая итерация начнётся только после завершения RefreshAsync. Поэтому медленный ответ увеличит фактический интервал, но проверки не наложатся друг на друга.

Внутри проверки передаём тот же token сетевому вызову:
using var response = await client.GetAsync(
    endpoint, token);
response.EnsureSuccessStatusCode();


При остановке фоновой службы token прервёт ожидание тика и поддерживающий отмену HTTP-запрос.

➡️ C# Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4🔥4
Как код C# превращается в машинные инструкции!

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

При запуске CLR загружает сборку. Когда вызывается метод, JIT-компилятор переводит его IL в машинный код для текущей платформы.

Сохрани, чтобы не потерять!

➡️ C# Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤4🔥3
Знаете, почему регулярное выражение может надолго занять поток?

При неудачном совпадении шаблоны с вложенными повторами могут проверять множество вариантов. На пользовательском вводе такая обратная проверка способна надолго загрузить процессор:
var pattern = "^(a+)+$";
var regex = new Regex(pattern);


По умолчанию Regex может работать без ограничения времени. Для обычного движка задаём конечный timeout при создании объекта:
var regex = new Regex(pattern, RegexOptions.None,
    TimeSpan.FromMilliseconds(100));


Если поиск не уложится в интервал, будет выброшено RegexMatchTimeoutException. Обрабатываем её отдельно и возвращаем понятную ошибку проверки:
try {
    return regex.IsMatch(input);
} catch (RegexMatchTimeoutException) {
    throw new ValidationException(
        "Regex validation timed out");
}


В .NET 7 и новее можно рассмотреть RegexOptions.NonBacktracking, если шаблон не использует неподдерживаемые конструкции вроде lookaround или backreference.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3
❤7👍5🔥3
Добавляем замер времени HTTP-запросов через DelegatingHandler!

Когда несколько сервисов обращаются к внешним API, можно видеть статус и длительность каждого запроса. Вынесем замер в обработчик HttpClient, чтобы не дублировать Stopwatch в каждом месте вызова.

Создаём обработчик, который получает логгер через конструктор:
sealed class TimingHandler(
    ILogger<TimingHandler> log) :
    DelegatingHandler
{
}


DelegatingHandler передаёт запрос следующему обработчику цепочки. Параметр primary constructor доступен методам класса, поэтому отдельное поле для log не требуется.

Переопределяем асинхронную отправку и сохраняем контракт базового класса:
protected override
    Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken token)


Токен отмены передаём дальше без замены, чтобы отмена вызывающего кода дошла до сетевого запроса.

Перед отправкой запускаем секундомер и готовим поле статуса:
var timer = Stopwatch.StartNew();
HttpStatusCode? status = null;


Переменная status остаётся null, если транспорт завершился исключением до получения ответа. Это позволяет логировать и успешные ответы, и неудачные попытки соединения.

Выполняем запрос через следующий обработчик и запоминаем статус:
try
{
    var response = await base.SendAsync(
        request, token);
    status = response.StatusCode;
    return response;
}


Ответ возвращается вызывающему коду без чтения или изменения его тела. Блок finally выполняется и после ответа, и при сетевом исключении.

В finally записываем метод, статус и длительность:
finally
{
    log.LogInformation(
        "{Method} {Status} {Ms}ms",
        request.Method, status,
        timer.ElapsedMilliseconds);
}


Регистрируем обработчик как transient и добавляем его в именованный клиент:
builder.Services.AddTransient<TimingHandler>();
builder.Services.AddHttpClient("catalog")
    .AddHttpMessageHandler<TimingHandler>();


Фабрика создаёт цепочки обработчиков для клиентов, поэтому один экземпляр DelegatingHandler нельзя переиспользовать в нескольких цепочках.

➡️ C# Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Почему Task.Run не делает синхронный ввод-вывод асинхронным?

Оборачивать блокирующее чтение файла в Task.Run кажется способом освободить текущий поток, но ожидание всё равно занимает поток из пула:
await Task.Run(() => File.ReadAllText(path));


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

Для чтения файла используем API с настоящей асинхронной операцией:
string text = await File.ReadAllTextAsync(
    path, cancellationToken);


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

Токен отмены передаём и в сам метод чтения, и дальше по цепочке:
await using var stream = File.OpenRead(path);
using var reader = new StreamReader(stream);
string text = await reader.ReadToEndAsync(token);


Для CPU-вычислений Task.Run может быть уместен, например чтобы не блокировать UI-поток:
var result = await Task.Run(
    () => Calculate(input, token), token);


Синхронная функция должна сама проверять отмену, потому что токен не прерывает уже запущенный код:
foreach (var item in items)
{
    token.ThrowIfCancellationRequested();
    Process(item);
}


Независимые операции с асинхронным API запускаем напрямую и ожидаем вместе:
Task<string> config = File.ReadAllTextAsync(
    configPath);
Task<string> data = File.ReadAllTextAsync(
    dataPath);
await Task.WhenAll(config, data);


Так ожидание I/O не расходует потоки на блокировку. В серверном коде Task.Run вокруг обычного ввода-вывода чаще добавляет переключение потоков, а не пропускную способность.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥2
Строим порядок сборки из зависимостей!

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

В этой задаче:
• строим граф зависимостей и считаем входящие связи
• обрабатываем готовые задачи через очередь
• проверяем, что в графе не осталось цикла


Алгоритм Кана проходит каждую задачу и каждую связь один раз.

➡️ C# Ready | #задача
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥3