.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1577. #ЗаметкиНаПолях
Загрузка Больших Файлов в
ASP.NET Core. Окончание
Начало

3. Теперь посмотрим на метод действия контроллера и сервис для загрузки файлов:
[HttpPost("upload")]
[MultipartFormData]
[DisableModelBinding]
public async Task<IActionResult> Upload()
{
var len = await _fileSvc
.UploadAsync(
HttpContext.Request.Body,
Request.ContentType);

return CreatedAtAction(nameof(Upload), len);
}

Здесь у нас есть метод действия HTTP POST, который имеет атрибуты, созданные в предыдущем посте, гарантирующие, что входящий запрос имеет правильный тип содержимого "multipart/form-data" и отключающие проверку модели.

Метод вызывает UploadAsync из сервиса FileService, который показан ниже:
public async Task<long> UploadAsync(
Stream file, string contentType)
{
long size = 0;
var bound =
HeaderUtilities.RemoveQuotes(
MediaTypeHeaderValue
.Parse(contentType).Boundary)
.Value;
var reader =
new MultipartReader(bound, file);
var sect =
await reader.ReadNextSectionAsync();

while (sect != null)
{
var fileSec = sect.AsFileSection();
if (fileSec is null) continue;

var path = Path.Combine("uploads",
fileSec.FileName);
await using var stream =
new FileStream(path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
1024);

await fileSec.FileStream?
.CopyToAsync(stream);

size += fileSec.FileStream.Length;

sect = await
reader.ReadNextSectionAsync();
}
return size;
}

Метод UploadAsync считывает составные данные из входного потока и сохраняет каждый файл на диск. Файлы должны быть разделены границей, определённой в заголовке Content-Type. Мы извлекаем эту границу в переменную bound, затем читаем каждый файл в цикле. Также вычисляем общий размер загруженных файлов.

Полный и более подробный код проекта на GitHub.

Посмотрим, как отправлять файлы с помощью Postman:
Нужно выбрать опцию form-data и добавить пары ключ-значение, где ключом будет имя файла, а значением - сам файл. Также проверьте, что установлен заголовок Content-Type со значением «multipart/form-data; border=…» на вкладке заголовков.

Итого
Загрузка больших файлов может быть сложной задачей, но использование потоков значительно упрощает этот процесс. Благодаря потокам мы можем эффективно читать и записывать данные небольшими порциями, сводя к минимуму использование памяти и позволяя обрабатывать большие файлы без проблем с производительностью.

Источник: https://code-maze.com/aspnetcore-upload-large-files/
👍16
День 1578. #Testing
Проверяем Юнит-Тесты с Помощью Мутационного Тестирования
Когда-то давно (практически на заре существования канала 😊) я уже писал про мутационное тестирование и как оно помогает проверить, правильно ли ваши юнит-тесты проверяют ваш код.

Недавно на канале JetBrains вышло видео, в котором Stefan Pölz, старший разработчик в Tryport, Microsoft MVP и контрибутор в JetBrains, подробно рассказывает, что такое мутационное тестирование, и как его интегрировать в Azure Pipelines или GitHub Actions.

https://youtu.be/9BoKyeZapLs
👍3
День 1579. #Карьера
Как Пережить Плохой День на Работе
Когда у вас плохой день, ваш разум зацикливается на неудачах. Вы теряете самообладание или плохо реагируете даже на незначительные неудобства. Может показаться, что все хотят вас достать, а мелкие конфликты перерастают в серьёзные проблемы. Хотя трудно искать светлую сторону вещей, когда разум забит негативом, есть четыре вещи, которые нужно сделать, чтобы плохой день не превратился в плохую неделю.

Шаг 1. Определите, чем это спровоцировано
Чем больше вы откладываете решение или признание того, что что-то не так, тем хуже становится. Психологи называют это эмоциональным избеганием. Избегать негативных эмоций вполне естественно, но вместо этого научитесь их принимать. Эмоции немного похожи на прогноз погоды. Их важно знать, учитывать и понимать, но они не являются решающим фактором в ваших жизненных планах. Не нужно отрицать плохую погоду или сосредоточивать на ней всё внимание, нужно её принять и скорректировать свои планы. Принятие эмоции позволяет превратить размышления в продуктивность, проблему в решение, а препятствие в возможность.
- Подумайте, что пошло не так.
- Не вините никого, не жалуйтесь. Определите, что можно сделать в следующий раз, чтобы этого не повторилось.
- Если другой человек является источником ваших страданий, не принимайте это на свой счёт. Чужое плохое поведение не отражает того, кем вы являетесь как личность.

Шаг 2. Позвольте себе замедлиться
Плохой день на работе не заставит ваши планы исчезнуть. Но разум не может сосредоточиться на работе. Большинство людей в этой ситуации либо:
- Уходят в самокритику, т.к не могут сделать запланированное.
- Заставляют себя действовать «через не могу».
Ругая себя за свои недостатки, вы только больше демотивируете себя. А превозмогание стресса может повлиять на психическое здоровье и личное благополучие. Вместо этого, проявите сочувствие к себе. Остановитесь, чтобы сказать себе: «сейчас мне действительно трудно», как я могу себе помочь? Исследования показывают, что сочувствие к себе тесно связано с эмоциональной устойчивостью и психологическим благополучием. Будьте добры к себе и отложите принятие важных решений или сделайте что-нибудь, что очистит ваш разум. Заботиться о себе, когда вы расстроены, важнее, чем быть продуктивным.

Шаг 3. Возродите позитивные связи
В плохой день всё, что заряжает вас энергией, обязательно изменит ваше настроение и поможет увидеть, что всё не так плохо, как казалось сначала. Сделайте паузу и определите, что вас заряжает энергией. Не обязательно пункт из списка дел и результат деятельности не имеет значения. Смысл в самоощущении. Небольшое позитивное действие разорвет негативный цикл размышлений и заменит его позитивными мыслями. Чтобы сохранить это чувство, подумайте о своих сильных сторонах и навыках, прошлых достижениях или обо всём, что вы сделали хорошо. Это вдохновит вас переключиться с деструктивных мыслей на созидательные действия. Вы всё ещё можете быть разочарованы событиями дня, но это разочарование не приведёт к ощущению безнадёжности и беспомощности.

Шаг 4: Работа на будущее
Взлеты и падения – то, что делает жизнь нескучной. Но в плохой день, легко поддаться негативу. Разум поглощен неприятностями, вы зацикливаетесь на вещах, которые даже не имеют значения в долгосрочной перспективе. Вместо этого спроецируйте плохой день на будущее:
- Как то, что произошло сегодня, влияет на ваши цели?
- Что это будет значить для вас через неделю, месяц, год?
- Это в вашей власти? Что вы можете сделать, чтобы этого не повторилось?
Так вы поймете, что тратите время и энергию на мелочи или заботы, которые в долгосрочной перспективе не будут иметь значения. Даже если это что-то важное, найти решение гораздо продуктивнее, чем погружаться в гнев и разочарование.

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

Источник: https://betterprogramming.pub/how-to-deal-with-a-bad-day-at-work-89d85ecc57bb
👍15
День 1580. #ЧтоНовенького
.NET 8 Расширяет Возможности Blazor и WebAssembly
Microsoft работает над повышением производительности веб-приложений с помощью рендеринга Blazor на стороне сервера и потокового рендеринга, а также улучшений среды выполнения Blazor WebAssembly.

На прошлой неделе Microsoft подробно изложили планы будущей среды разработки .NET 8. Для веб-разработки .NET 8 будет сочетать сильные стороны рендеринга на стороне сервера и на стороне клиента в компонентной модели Blazor.

Теперь доступный в версии превью 4 .NET 8 использует отрисовку на стороне сервера с компонентами Blazor, улучшенную навигацию и обработку форм, а также потоковую отрисовку. По словам Microsoft, потоковая отрисовка, при которой обновления контента передаются в поток ответов, может улучшить взаимодействие с пользователем для страниц, генерируемых на стороне сервера, которым необходимо выполнять длительные асинхронные задачи для полной визуализации.

Например, чтобы отобразить страницу, вам может потребоваться выполнить длительный запрос к базе данных или вызов API. Обычно все асинхронные задачи, выполняемые как часть рендеринга страницы, должны быть завершены до отправки обработанного ответа, что может привести к задержке загрузки страницы. Потоковая визуализация изначально отображает всю страницу с содержимым-заполнителем во время выполнения асинхронных операций. После завершения асинхронных операций обновленное содержимое отправляется клиенту по тому же ответному соединению, а затем Blazor вносит исправления в DOM. Преимущество такого подхода заключается в том, что основной макет приложения отображается максимально быстро, а страница обновляется, как только контент готов.

Разработчики смогут добавлять клиентскую интерактивность для каждого компонента или страницы в отдельности, а также выбирать режим рендеринга во время выполнения. Рендеринг на стороне сервера и потоковый рендеринг стали доступны в .NET 8 Preview 3 и .NET 8 Preview 4 соответственно. Дополнительные механизмы будут добавлены в следующих предварительных версиях.

В .NET 8 разработчики также могут взять компонент Blazor и отобразить его полностью вне контекста HTTP-запроса. Компонент может отображаться через HTML как строка или поток, независимо от среды размещения (серверной или WebAssembly). Это полезно для создания фрагментов HTML, таких как автоматическое электронное письмо. В Microsoft заявляют, что в будущем это позволит генерировать статический контент сайта для Blazor.

Microsoft также работает над улучшением производительности .NET в браузерах с помощью WebAssembly. Jiterpreter в .NET 8 предлагает частичную поддержку JIT и повышает производительность среды выполнения .NET WebAssembly. Microsoft сообщает, что рендеринг пользовательского интерфейса в тестах стал на 20% быстрее благодаря jiterpreter, а сериализация и десериализация JSON выполняются в два раза быстрее. Последние спецификации WebAssembly, такие как SIMD для предварительной (AOT) компиляции, наряду с улучшениями горячей перезагрузки, также используются для WebAssembly.

Также для приложений Blazor WebAssembly используется новый удобный для Интернета формат упаковки Webcil. Webcil представляет собой веб-ориентированную упаковку сборок .NET, которая удаляет всё содержимое, относящееся к нативному выполнению Windows, чтобы избежать проблем при развертывании в средах, которые блокируют загрузку или использование DLL-файлов.

Источники:
-
https://www.infoworld.com/article/3697728/microsoft-net-8-boosts-blazor-webassembly.html
-
https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-4/
👍14
День 1581. #ЗаметкиНаПолях
Мультитенантные Приложения с EF Core. Начало
Сегодня большинство программ основано на концепции мультитенантости, т.е. обслуживания нескольких клиентов, сохраняя при этом их данные изолированными. Это можно реализовать двумя способами:
1. Единая база данных и логическая изоляция,
2. Несколько баз данных и физическая изоляция.

1. Мультитенантость в единой базе данных
Для реализации мультитенантости понадобятся две вещи:
- Узнать текущего клиента,
- Отфильтровать данные только для этого клиента.

Типичный подход к мультитенантости в одной базе данных — наличие столбца TenantId в таблицах. А затем фильтрация по этому столбцу при запросе к базе данных. В EF Core мы можем использовать глобальный фильтр запросов. Внутри метода OnModelCreating мы настраиваем фильтр запросов для объекта Order:
public class OrdersContext : DbContext
{
private string _tenantId;

public OrdersContext(
DbContextOptions<OrdersContext> opt,
TenantProvider tp) : base(opt)
{
_tenantId = tp.TenantId;
}

protected override void
OnModelCreating(ModelBuilder mb)
{
mb.Entity<Order>
.HasQueryFilter(o =>
o.TenantId == _tenantId);
}
}

Мы используем класс TenantProvider для получения текущего клиента:
public class TenantProvider
{
private IHttpContexAccessor _hca;

public TenantProvider(IHttpContexAccessor hca)
{
_hca = hca;
}

public string TenantId => _hca
.HttpContext.Request.Headers["X-TenantId"];
}

В этом примере TenantId получается из заголовка HTTP-запроса. Также его можно извлекать из строки запроса, JWT Claim, ключа API и т.п. Последние два варианта более безопасны.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core
👍15
День 1582. #ЗаметкиНаПолях
Мультитенантные Приложения с EF Core. Окончание
Начало

2. Мультитенантность в раздельных базах данных с помощью EF Core
В некоторых отраслях, таких как здравоохранение, требуется высокая степень изоляции данных, и использование базы данных для каждого клиента является обязательным. Если мы хотим изолировать каждого клиента в отдельной базе данных, нам нужно:
- Применить разные строки подключения для каждого клиента,
- Каким-то образом определять строку подключения для каждого клиента.

Здесь нельзя использовать фильтры запросов, так как мы работаем с разными базами. Поэтому нужно где-то хранить информацию о клиенте и строке подключения. Простым примером (если клиентов относительно немного) может быть сохранение их в настройках приложения:
"Tenants": {
{ "Id": "tenant-1", "ConnectionString":
"Host=tenant1.db;Database=tenant1" },
{ "Id": "tenant-2", "ConnectionString":
"Host=tenant2.db;Database=tenant2" }
}
Затем можно зарегистрировать экземпляр IOptions со списком объектов Tenant. Вот здесь подробнее про паттерн Options.

Также нужно изменить класс TenantProvider, чтобы он возвращал строку подключения для текущего клиента:
public sealed class TenantProvider
{
private IHttpContexAccessor _hca;
private TenantSettings _ts;

public TenantProvider(
IHttpContexAccessor hca,
IOptions<TenantSettings> opt)
{
_hca = hca;
_ts = opt.Value;
}

public string TenantId => _hca
.HttpContext.Request
.Headers["X-TenantId"];

public string GetConnectionString()
{
return _ts.Tenants
.Single(t => t.Id == TenantId);
}
}

И последняя часть — это регистрация DbContext для динамического разрешения строки подключения для текущего клиента:
builder.Services
.AddDbContext<OrdersContext>((sp, o) =>
{
var tp = sp.GetRequiredService<TenantProvider>();
var cs = tp.GetConnectionString();

o.UseSqlServer(cs);
});

При каждом запросе мы создаём новый OrdersContext и подключаемся к соответствующей базе данных для этого клиента. Обязательно следует хранить строки подключения клиента в безопасном месте, таком как Azure Key Vault.

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

Источник: https://www.milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core
👍11
День 1583. #ЗаметкиНаПолях #TipsAndTricks
Интересные Вещи, Которые Можно Делать с Кортежами
Сегодня рассмотрим, что такое кортежи и некоторые вещи, которые можно сделать с ними.

Кортежи (ValueTuple) в C# — это упрощённый изменяемый тип-значение, который позволяет группировать несколько значений или связанных данных вместе для различных целей, таких как возврат нескольких значений из метода, создание структурированных данных и т.п. Он обеспечивает более высокую производительность по сравнению с традиционными ссылочными типами, поскольку не требует аллокации на куче.

(string Name, int Age) person = ("Jon Smith", 42);
person.Name; // "Jon Smith"
person.Age; // 42

static (string Name, int Age) GetPerson()
=> ("Jon Smith", 42);

Что же можно делать с помощью кортежей?

1. Поменять местами два значения
Допустим, мы хотим поменять местами значения двух переменных:
int a = 1;
int b = 2;
// Замена
(a, b) = (b, a);

2. Простая реализация IEquatable
А также простой способ сравнения двух объектов или получения хэш-кода:
public class Dimension : IEquatable<Dimension>
{
public Dimension(int width, int height)
=> (Width, Height) = (width, height);

public int Width { get; }
public int Height { get; }

public bool Equals(Dimension d)
=> (Width, Height) == (d?.Width, d?.Height);

public override bool Equals(object o)
=> o is Dimension d && Equals(d);

public override int GetHashCode()
=> (Width, Height).GetHashCode();
}

3. Сопоставление по шаблону
Кортежи позволяют писать элегантный и выразительный код:
(string Name, int Age) person = ("Alice", 25);
string desc = person switch
{
("Alice", >= 0 and < 13) => "Alice ребёнок.",
("Alice", >= 13 and < 20) => "Alice подросток.",
("Alice", _) => "Alice взрослая.",
(_, >= 0 and < 13) => "Это ребёнок.",
(_, >= 13 and < 20) => "Это подросток.",
_ => "Это взрослый."
};

4. Деконструкция пользовательских типов
Кортежи позволяют легко разбить тип на отдельные переменные:
public class Student
{
public string Name { get; set; }
public int Age { get; set; }

public void Deconstruct(out string name, out int age)
{
name = Name;
age = Age;
}
}

var student = new Student { Name = "John", Age = 21 };
var (studentName, studentAge) = student;

См. подробнее о деконструкции

Источник: https://steven-giesel.com/blogPost/bcbb602f-4080-4e89-9929-019bb4e1cb58
👍23
День 1584.
Папки Против Пространств Имён
Что, если ваша структура папок не будет соответствовать структуре пространств имён?

Поскольку для новичков большинство вещей впервой, иногда они делают что-то не так, как мы обычно делаем. Например, они знают про пространства имён, но могут не понимать, что структура файлов и папок должна отражать структуру пространств имён. Компилятору все равно, но я всегда делал это так. Для этого даже есть правило статического анализатора. Но в самом деле, что будет, если нарушить это соглашение?

Инструменты и шаблоны часто подталкивают вас к организации кода в соответствии с техническими проблемами: контроллеры, модели, представления и т. д. Код организован в папки, такие как Controllers, Models, DataAccess и т. п. Вы кладёте все контроллеры в папку Controllers и убеждаетесь, что пространство имён совпадает иерархии папок. Распространённой критикой является то, что это неправильный способ организации кода. Но это подразумевает, что есть правильный способ. Например, файлы должны быть организованы по папкам в соответствии с функциональностью, а не с техническим предназначением.

Однако если вы решите игнорировать соглашение о том, что структура пространств имён должна отражать структуру папок, у вас появится вторая ось вариативности. Например, если оставить организацию файлов по папкам в соответствии с техническим предназначением, но организовать пространства имён в соответствии с функциями: Users, Orders, Reservations и т.д., то открыв в IDE окно Class View, можно заметить, что иерархия классов там отличается от иерархии в Обозревателе Решений (Solution Explorer), классы организованы по функциям.

Этот пост не является рекомендацией. Это не что иное, как первоначальное исследование идеи. Несмотря на то, что это даёт две оси вариативности, вам всё равно придётся идти на компромиссы. С контроллерами всё понятно, но куда вы поместите сквозные задачи или общую логику предметной области?

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

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

Итого
Теоретически структура пространства имён может не следовать структуре каталогов. Вы можете реорганизовать кодовую базу так, чтобы файловая структура соответствовала деталям реализации, а иерархия пространств имен — функциям (или наоборот). Я это попробовал в своём проекте, и после нескольких часов реорганизации кода, не могу сказать, что меня пугает эта идея. Хотя, возможно, в более крупной базе кода она столкнётся с проблемами. С другой стороны, я все ещё не убежден, что навязывание иерархии сложному дизайну программного обеспечения как-то особенно выгодно.

Источник: https://blog.ploeh.dk/2023/05/15/folders-versus-namespaces/
Автор оригинала: Марк Симанн
👍8
День 1585. #ЧтоНовенького
Единые Настройки в Visual Studio
Разработчики Visual Studio решили упростить настройку IDE для всех ваших рабочих сред, независимо от того, работаете ли вы на ноутбуке дома, на компьютере в офисе или используете Dev Box или виртуальную машину Azure. Единые Настройки (Unified Settings) – прототип, который ещё находится в стадии разработки, призванный переосмыслить то, как вы персонализируете свою IDE и совместно использовать настройки между различными рабочими средами и установками Visual Studio.

Единые настройки помогают настроить среду как индивидуальными параметрами, так и параметрами группы/рабочей области. Настройки синхронизируются с облаком, поэтому настройка новой среды может занять минуты, а не часы. У вас будет доступ ко всем вашим настройкам в файле JSON на случай, если вы захотите просмотреть их или поделиться ими с товарищем по команде. На форуме сообщества создана тема, посвящённая прототипу единых настроек, где разработчики утилиты собирают мнения и отзывы по мере её реализации. В этом коротком видео поясняется суть утилиты и её отличие от текущего UI настроек.

Сегодня опыт настройки IDE под свои предпочтения полон препятствий и боли. Это приводит к тому, что многие варианты кастомизации никогда не используются или даже не обнаруживаются. Единые настройки призваны решить некоторые распространённые проблемы:
1. Последовательность
Сегодня каждая страница в параметрах инструментов имеет собственный UI. Поэтому часто параметры не отражают текущую тему, непоследовательно масштабируются под разрешение монитора или используют разный UI для похожих настроек и т.п.

2. Область применения
Сейчас настройки обычно меняют поведение этой конкретной установки VS. Но иногда может потребоваться управлять настройками, которые лучше всего применять к целым репозиториям. В Единых Настройках представлены «области действия», чтобы предоставить больше гибкости и контроля над вашей IDE. Предполагаются следующие области применения:
- Пользовательская (User): применяется ко всем проектам и решениям для данной установки Visual Studio. Как правило, это личные настройки: тема, шрифты и цвета, параметры отладки.
- Рабочей области (Workspace): применяется к конкретному проекту или репозиторию. Может включать специальные настройки отладки, настраиваемый внешний вид вкладки и т.п.

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

4. Возможность обнаружения
В текущих параметрах множество вкладок настроек, поэтому часто бывает трудно найти нужный параметр. Поиск не всегда показывает полные результаты, и их трудно анализировать. В единых настройках при поиске термина все результаты поиска будут отображаться в одном представлении. Вы сможете редактировать каждый параметр непосредственно в этом представлении. Также будет добавлена возможность фильтрации, чтобы просматривать категории параметров, такие как «Производительность» и «Специальные возможности». Также изменение настройки по умолчанию будет видимо с возможностью легко откатиться назад.

Итого
Разработчики утилиты включили в обсуждение несколько открытых вопросов по утилите и ждут ваших мнений или, возможно, вопросов, которые они не рассмотрели.

Источник: https://devblogs.microsoft.com/visualstudio/unifiedsettings/
👍9
День 1586. #Карьера
Работа Требует Внимания. Постоянные Оповещения Крадут Его
Сложные проблемы требуют сосредоточенного и постоянного внимания со стороны разработчиков. Это внимание — время и свобода концентрации — самый ценный ресурс команды.

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

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

Мы регулярно совмещаем несколько задач. Состояние потока требует постоянного внимания в течение долгого времени. Однако переключение между задачами сопряжено с умственными затратами, и эти затраты приводят к тому, что на выполнение задач уходит до 40% больше времени. Проскальзывают мелкие ошибки, опечатки, забываются детали. Даже попытка делать только два дела одновременно может означать, что вы делаете оба плохо.

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

Чтобы хорошо работать в течение дня, нужно какое-то время, когда вы не работаете. Время простоя необходимо для снижения стресса, предотвращения эмоционального выгорания и поддержания здоровья мозга. Поскольку мы всё больше работаем из дома, работа проникает в нашу домашнюю жизнь. Наши телефоны заполнены уведомлениями, как и наши рабочие компьютеры, но телефон всегда при нас. Привычка быть «всегда на связи» не позволяет игнорировать уведомления, даже в нерабочее время. Вы можете — и, вероятно, должны — отключать звук и игнорировать эти уведомления в нерабочее время. Некоторые чат-программы устанавливают рабочее время, в течение которого будут отправляться уведомления. В течение рабочего дня вы можете контролировать статусы и настройки уведомлений, указывая, когда вы доступны, а когда нет.

Но можно переосмыслить, зачем нам вообще уведомления, как и собрания. Письмо или запись в чате — это приглашение к сотрудничеству и обмену информацией. Отлично! Но уведомления синхронны; они требуют внимания прямо сейчас. Сотрудничество эффективнее, когда все заинтересованные стороны собирались и готовы, либо когда оно асинхронно, чтобы у них было время уделить внимание проблеме.

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

Менять рабочие процессы сложно. В конечном счёте проблемы утечки внимания имеют культурные решения. У вас могут быть правильные инструменты для реализации этого, но в конечном счёте есть два вопроса, на которые организация должна ответить: (а) Как вы сотрудничаете? и (б) Как вы делитесь знаниями? До тех пор, пока ответы на них включают аналоговую или цифровую версию «похлопывания коллеги по плечу», чтобы привлечь его внимание, мы будем отвлекаться и постоянно пытаться вернуться в контекст, который у нас был до того, как нас отвлекли.

Источник: https://stackoverflow.blog/2023/05/22/modern-work-requires-attention-constant-alerts-steal-it/
👍6
День 1587. #ЗаметкиНаПолях #DevOps
Создаём Конвейер CI/CD в GitHub Actions
Благодаря CI/CD вы можете значительно сократить объем ручных операций и больше сосредоточиться на создании ПО, обеспечивая более быстрые и надёжные развёртывания.

CI/CD — это метод автоматизации рабочего процесса разработки и развёртывания ПО. Непрерывная интеграция (CI) - это процесс автоматизации синхронизации нового кода с репозиторием. Любые изменения в коде приложения немедленно собираются, тестируются и объединяются. Непрерывная доставка или развёртывание (CD) обеспечивает развёртывание изменений в производственную (или другую) среду.

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

Нужно создать рабочий процесс, который будет запускаться, когда в вашем репозитории происходит какое-либо событие. Примеры событий: коммит в основной ветке, создание тега или запуск рабочего процесса вручную. Например, для сборки и тестирования:
name: Build & Test

on:
push:
branches:
- main

env:
DOTNET_VERSION: "7.0.x"

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Install dependencies
run: dotnet restore WebApi

- name: Build
run: dotnet build WebApi --configuration Release --no-restore

- name: Test
run: dotnet test WebApi --configuration Release --no-build

Здесь мы:
- Определяем событие для запуска рабочего процесса (при коммите в ветку main)
- Настраиваем.NET SDK с версией из env.DOTNET_VERSION
- Восстанавливаем зависимости, затем собираем и тестируем проект с помощью команд dotnet CLI

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

Непрерывная доставка - реальная ценность процесса CI/CD. Вы делаете изменение, и уже через несколько минут ваш код собран, протестирован и выложен в рабочую среду. Вот пример сценария развёртывания:
name: Publish

on:
push:
branches:
- main

env:
AZURE_WEBAPP_NAME: web-api
AZURE_WEBAPP_PACKAGE_PATH: "./publish"
DOTNET_VERSION: "7.0.x"

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Build and Publish
run: |
dotnet restore WebApi
dotnet build WebApi -c Release --no-restore
dotnet publish WebApi -c Release --no-build
--output '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'

- name: Deploy to Azure
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
package: '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'

Он очень похож на предыдущий, со следующими отличиями:
- Добавление шага публикации и настройка выходного пути
- Использование действия azure/webapps-deploy@v2 для развёртывания в Azure.

Если вам нужно безопасно и надёжно использовать секретные данные в рабочих процессах, вы можете определить секреты GitHub и использовать их в действиях, не добавляя их в систему управления версиями. В примере выше использован secrets.AZURE_PUBLISH_PROFILE для доступа к профилю публикации в экземпляре App Service.

Источник: https://www.milanjovanovic.tech/blog/build-ci-cd-pipeline-with-github-actions-and-dotnet
👍14
День 1588. #ЗаметкиНаПолях
Полезные Расширения для Task<T>
Сегодня рассмотрим 5 полезных методов расширения для Task.

1. Запустил и забыл
Иногда нужно начать задачу и не ждать её завершения. Например, для некритических задач, когда не важен результат. Создадим метод расширения FireAndForget. При желании можно передать методу обработчик ошибок, который будет вызываться, когда задача выдаёт исключение:
public static void FireAndForget(
this Task task,
Action<Exception> errorHandler = null)
{
task.ContinueWith(t =>
{
if (t.IsFaulted && errorHandler != null)
errorHandler(t.Exception);
},
TaskContinuationOptions.OnlyOnFaulted);
}

Использование:
SendEmailAsync().FireAndForget(
e => Console.WriteLine(e.Message));

2. Повтор
Чтобы повторять задачу определённое количество раз, создадим метод расширения Retry. Он будет повторять задачу до тех пор, пока она не завершится успешно или не будет достигнуто максимальное количество попыток. Можно передать задержку между попытками:
public static async Task<TResult> 
Retry<TResult>(
this Func<Task<TResult>> taskFactory,
int maxRetries,
TimeSpan delay)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await taskFactory()
.ConfigureAwait(false);
}
catch
{
if (i == maxRetries - 1)
throw;
await Task.Delay(delay)
.ConfigureAwait(false);
}
}
// не должно достигать этого места
return default(TResult);
}

Использование:
var result = await (
() => GetResultAsync())
.Retry(3, TimeSpan.FromSeconds(1));

3. Действие при сбое
Выполняется при возникновении исключения в задаче:
public static async Task 
OnFailure(this Task task,
Action<Exception> onFailure)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception ex)
{
onFailure(ex);
}
}

Использование:
await GetResultAsync()
.OnFailure(ex => Console.WriteLine(ex.Message));

4. Тайм-аут
Установка тайм-аута для задачи полезна, когда вы хотите, чтобы задача не выполнялась слишком долго (подойдёт, если задача не принимает токена отмены):
public static async Task 
WithTimeout(this Task task,
TimeSpan timeout)
{
var delayTask = Task.Delay(timeout);
var completedTask = await
Task.WhenAny(task, delayTask)
.ConfigureAwait(false);
if (completedTask == delayTask)
throw new TimeoutException();

await task;
}

Использование:
await GetResultAsync()
.WithTimeout(TimeSpan.FromSeconds(1));

Замечание: Начиная с .NET 6 для этой цели можно использовать WaitAsync.

5. Результат по умолчанию
Иногда нужно вернуть результат по умолчанию при сбое задачи:
public static async Task<TResult> 
Fallback<TResult>(
this Task<TResult> task,
TResult fallbackValue)
{
try
{
return await task.ConfigureAwait(false);
}
catch
{
return fallbackValue;
}
}

Использование:
var result = await GetResultAsync()
.Fallback("fallback");

Источник: https://steven-giesel.com/blogPost/d38e70b4-6f36-41ff-8011-b0b0d1f54f6e
👍26
День 1589. #ЗаметкиНаПолях
Планирование Фоновых Заданий в
Quartz.NET
Если вы создаёте масштабируемое приложение, обычно требуется перенести часть работы в фоновое задание. Несколько примеров:
- отправка email уведомлений,
- создание отчётов,
- обновление кэша,
- обработка изображений.

Quartz.NET — полнофункциональная система планирования заданий с открытым кодом, которую можно использовать от самых маленьких приложений до крупных корпоративных систем.

В Quartz.NET необходимо понимать три концепции:
1. Job — фоновое задание.
2. Trigger — триггер, управляющий запуском задания.
3. Scheduler — планировщик, отвечающий за координацию заданий и триггеров.

Во-первых, добавим пакет Quartz.Extensions.Hosting. Этот вариант установки прекрасно интегрируется с .NET с помощью экземпляра IHostedService.

Теперь надо внедрить и запустить сервис:
services.AddQuartz(cfg =>
{
cfg.UseMicrosoftDependencyInjectionJobFactory();
});
services.AddQuartzHostedService(opt =>
{
opt.WaitForJobsToComplete = true;
});

Quartz.NET будет создавать задания, извлекая их из DI-контейнера. Это также означает, что вы можете использовать scope-сервисы, а не только singleton или transient. Установка WaitForJobsToComplete в true гарантирует, что Quartz.NET будет ожидать корректного завершения заданий перед выходом.

Чтобы создать фоновое задание, необходимо реализовать IJob. В нём один метод — Execute, где размещается код фонового задания. Здесь отметим несколько вещей:
- можно использовать DI для внедрения сервисов,
- атрибут DisallowConcurrentExecution предотвращает одновременный запуск задания несколько раз.
[DisallowConcurrentExecution]
public class ProcessMessages : IJob
{
// внедряем нужные сервисы
public async Task Execute(
IJobExecutionContext context) { … }
}

Теперь зарегистрируем задание в DI-контейнере с триггером (планировщик Quartz создаст сам):
services.AddQuartz(cfg =>
{
var key = new JobKey(nameof(ProcessMessages));
cfg
.AddJob<ProcessMessages>(key)
.AddTrigger(t => t.ForJob(key)
.WithSimpleSchedule(s =>
s.WithIntervalInSeconds(10)
.RepeatForever()
)
);

cfg.UseMicrosoftDependencyInjectionJobFactory();
});

Здесь:
1) Однозначно идентифицируем фоновое задание с помощью JobKey.
2) AddJob регистрирует ProcessMessages в DI, а также в Quartz.
3) Настраиваем триггер для задания, вызывая AddTrigger:
- связываем задание с триггером с помощью ForJob,
- настраиваем расписание: в этом примере запускаем задание каждые десять секунд и повторяем бесконечно, пока работает сервис.

Quartz также поддерживает настройку триггеров с помощью выражений cron.

По умолчанию Quartz настраивает все задания, используя RAMJobStore, который является наиболее производительным, поскольку хранит все данные в оперативной памяти. Однако это также означает, что хранилище непостоянное, и вы можете потерять информацию о планировании, когда приложение остановится или выйдет из строя. Иногда может быть полезно иметь постоянное хранилище, для этого есть встроенный AdoJobStore, который работает с базами данных SQL. Нужно создать набор таблиц базы данных для использования Quartz.NET. Подробности описаны в документации.

Источник: https://www.milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net
👍23
День 1590. #ЧтоНовенького
Действия для Устаревших Репозиториев GitHub
По мере расширения организации и развития проектов репозитории могут легко устаревать и становиться неактивными. Важно идентифицировать эти репозитории и предпринимать соответствующие действия, чтобы исходный код был организован и обновлён. Архивирование проектов, которые больше не находятся в активной разработке, сообщит остальным разработчикам, что полагаться на поддержку этого проекта больше не стоит.

GitHub представляет Stale Repos Action, инструмент, который поможет выявлять и сообщать о репозиториях, в которых не было активности. Таким образом вы можете обнаружить неактивные репозитории в своей организации, что позволит вам принять решение об их архивации, возрождении или переориентации усилий.

Чтобы начать использовать Stale Repos Action, выполните следующие действия:
1. Создайте новый репозиторий или используйте существующий для размещения рабочего процесса GitHub Action. Это будет репозиторий, в который вы получите отчёт.
2. Скопируйте пример рабочего процесса, приведённый ниже, в свой репозиторий, сохранив его в каталоге .github/workflows/ с расширением .yml, Например, .github/workflows/stale_repos.yml. Обратите внимание, что в коде ниже по умолчанию используется один год. Используйте нужное вам целое количество дней с момента последнего коммита в ветку по умолчанию.
3. Настройте необходимые значения среды, в примере рабочего процесса, приведённом ниже. Значения GH_TOKEN и ORGANIZATION следует заменить вашей информацией из секретов репозитория. См. подробнее, как их создать.
Примечание. Убедитесь, что ваш токен GitHub имеет доступ для чтения ко всем репозиториям в организации. GitHub использует токен личного доступа (классический) с выбранной областью read:org.

Пример:
name: stale repo identifier

on:
workflow_dispatch:
schedule:
- cron: '3 2 1 * *'

jobs:
build:
name: stale repo identifier
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Run stale_repos tool
uses: docker://ghcr.io/github/stale_repos:v1
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
ORGANIZATION: ${{ secrets.ORGANIZATION }}
INACTIVE_DAYS: 365

- name: Create issue
uses: peter-evans/create-issue-from-file@v4
with:
title: Stale repository report
content-filepath: ./stale_repos.md
assignees: <ВАШ_ЛОГИН_GITHUB>

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

См. также: Подробнее о GitHub Actions.

Источник: https://github.blog/2023-06-05-announcing-the-stale-repos-action/
👍8
День 1591. #ЗаметкиНаПолях
Повышаем Производительность с Помощью Разделения Запросов в EF Core
Некоторые сложные многотабличные запросы могут выполняться слишком долго в зависимости от количества данных в таблицах.

Допустим, у нас есть таблица заказов Orders. Заказ может иметь одну или несколько позиций LineItems. Кроме того, для позиций заказов есть таблица, содержащая допустимые измерения — LineItemDimensions. Вот запрос, который нам нужен:
dbContext
.Orders
.Include(o => o.LineItems)
.ThenInclude(li => li.Dimensions)
.First(o => o.Id == orderId);

EF Core преобразует это в следующий SQL-запрос:
SELECT o.*, li.*, d.*
FROM Orders o
LEFT JOIN LineItems li ON li.OrderId = o.Id
LEFT JOIN LineItemDimensions d ON d.LineItemId = li.Id
WHERE o.Id = @orderId
ORDER BY o.Id, li.Id, d.Id;

В большинстве случаев этот запрос будет выполняться нормально. Однако здесь мы сталкиваемся с проблемой декартова взрыва - ситуацией, когда на выбор N заказов с M позициями и K измерениями база данных возвращает N*M*K строк.

В EF Core 5.0 появилась новая функция разделения запросов. Она позволяет указать, что данный LINQ-запрос должен быть разделён на несколько SQL-запросов. Всё, что нужно сделать, это вызвать метод AsSplitQuery:
dbContext
.Orders
.Include(o => o.LineItems)
.ThenInclude(li => li.Dimensions)
.AsSplitQuery()
.First(o => o.Id == orderId);

В этом случае EF Core сгенерирует следующие SQL-запросы:
SELECT o.*
FROM Orders o
WHERE o.Id = @orderId;

SELECT li.*
FROM LineItems li
JOIN Orders o ON li.OrderId = o.Id
WHERE o.Id = @orderId;

SELECT d.*
FROM LineItemDimensions d
JOIN LineItems li ON d.LineItemId = li.Id
JOIN Orders o ON li.OrderId = o.Id
WHERE o.Id = @orderId;
Обратите внимание, что для каждого оператора Include у нас есть отдельный запрос. Преимущество здесь в том, что мы не выбираем лишние данные, как это было в предыдущем случае.

Вы можете включить разделение запросов на уровне контекста, вызвав метод UseQuerySplittingBehavior:
services.AddDbContext<ApplicationDbContext>(opt =>
opt.UseSqlServer(
"CONNECTION_STRING",
o => o.UseQuerySplittingBehavior(
QuerySplittingBehavior.SplitQuery)));

Это приведет к тому, что все запросы, создаваемые EF Core, будут разделёнными запросами. Чтобы вернуться к единому SQL-запросу в этом случае, в LINQ-запрос нужно будет добавить AsSingleQuery().

Особенности
- Не существует гарантии согласованности для нескольких SQL-запросов. Вы можете столкнуться с проблемой, если одновременно с запросом данных выполняется параллельное обновление. Чтобы смягчить это, можно обернуть запросы в транзакцию, но это также может привести к проблемам с производительностью.
- Каждый запрос требует отдельного обращения к БД по сети. Это может снизить производительность, если задержка для базы высока.

Источник: https://www.milanjovanovic.tech/blog/entity-framework-query-splitting
👍21👎1
День 1592. #Карьера
Маркетинговые Приёмы для Создания Резюме
Резюме — первый контакт с потенциальным работодателем. Отправка одного и того же резюме на каждую вакансию не будет самым быстрым и продуктивным методом.

Думайте как маркетолог
У многих инженеров аллергия на маркетинг. Но маркетинг – это способность донести идею таким образом, чтобы она была проста для понимания и убедительна для человека. Т.е. поставить себя на место другого человека и рассказать о своих услугах так, чтобы они соответствовали его интересам и проблемам. Нужно попытаться понять, кто наши потенциальные покупатели: что им нравится, что они делают. Отталкиваясь от этого, создать презентацию, описав основные функции и преимущества, которые понравятся конкретному человеку. Не заставлять его искать ваши выгодные стороны, а проделать эту работу за него. Для опубликованных вакансий это сделать довольно просто: они точно говорят вам, что им нужно.

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

Первый фильтр резюме - система отслеживания кандидатов (ATS): ПО, которое отсеивает резюме без нужных ключевых слов. Поэтому важно, чтобы ваше резюме могло быть легко прочитано ATS. Чистое форматирование, отсутствие диаграмм, рисунков, отсутствие важной информации в верхних и нижних колонтитулах.

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

Хотя и не стоит рассылать базовое резюме, оно может стать основой вашего профиля LinkedIn. Большинство рекрутеров просматривают профили LinkedIn как часть процесса найма.

Целевые резюме
В целевом резюме надо сообщить менеджеру по найму, почему вы подходите для конкретной роли. Сгруппируйте вакансии по типам и создайте отдельные резюме для каждого типа. Резюме на вакансию фуллстек-разработчика в стартап и на инженера по облачным сервисам в крупную корпорацию должны отличаться, даже если у вас есть все подходящие навыки. Если требуется конкретный навык, убедитесь, что он указан.

Остальное зависит от конкретной вакансии. В любое резюме вы можете добавить «tl;dr» — саммари вашей карьеры, ориентированное на должность, на которую вы претендуете. Это послужит тизером, чтобы резюме дочитали до конца. Не обязательно красиво расписывать, нужно донести свою историю как можно чётче и эффективнее, т.е. использовать простой язык и простое построение предложений.

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

Итого
Когда вы ищете работу, вашим потенциальным клиентом является менеджер по найму. Резюме часто является первой возможностью произвести впечатление, поэтому чем больше мыслей вы сможете в него вложить, тем лучше. Создав несколько резюме, адаптированных для различных должностей, отраслей и размеров компаний, вы получите больше шансов пройти отбор.

Источник: https://stackoverflow.blog/2023/05/24/how-to-use-marketing-techniques-to-build-a-better-resume/
👍14
День 1593. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
I. Управление Памятью и Сборка Мусора
Управление памятью и сборка мусора являются важными аспектами настройки производительности в C#, поэтому эти рекомендации помогут вам оптимизировать код для достижения максимальной эффективности.

1. Используйте интерфейс IDisposable
IDisposable помогает правильно управлять неуправляемыми ресурсами и обеспечивает эффективное использование памяти вашим приложением.
Плохо:
public class ResourceHolder
{
private Stream _stream;

public ResourceHolder(string filePath)
{
_stream = File.OpenRead(filePath);
}
//…
}

ResourceHolder не реализует интерфейс IDisposable, поэтому неуправляемые ресурсы могут не освобождаться, что приводит к утечке памяти.
Хорошо:
public class ResourceHolder : IDisposable
{
private Stream _stream;

public ResourceHolder(string filePath)
{
_stream = File.OpenRead(filePath);
}

public void Dispose()
{
_stream?.Dispose();
}
}

Реализуя интерфейс IDisposable, вы гарантируете, что неуправляемые ресурсы будут освобождены, когда они больше не нужны, что предотвратит утечку памяти и уменьшит нагрузку на сборщик мусора.

Хотя, с IDisposable не всегда бывает так просто. Подробнее об этом можно почитать в серии постов Мои любимые ошибки с IDisposable:
- 1
- 2
- 3

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

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

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

См. подробнее про оптимизацию кода.

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍15
День 1594. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
II. Асинхронное программирование и async/await
Асинхронное программирование — мощный метод повышения производительности в операциях, связанных с вводом-выводом, позволяющий повысить скорость отклика и эффективность приложения.

1. Ограничьте количество одновременных операций
Управление параллелизмом имеет решающее значение для оптимизации производительности. Ограничивая количество одновременных операций в приложении, вы помогаете снизить общую нагрузку на систему.
Плохо:
public async Task ProcessManyItems(List<string> items)
{
var tasks = items.Select(
async item => await ProcessItem(item));
await Task.WhenAll(tasks);
}

Здесь задачи создаются одновременно для каждого элемента без надлежащего ограничения, что может вызвать значительную нагрузку на систему.
Хорошо:
public async Task ProcessManyItems(
List<string> items,
int maxConcurrency = 10)
{
using (var semaphore = new
SemaphoreSlim(maxConcurrency))
{
var tasks = items.Select(async item =>
{
// Ограничиваем конкурентность семафором
await semaphore.WaitAsync();
try
{
await ProcessItem(item);
}
finally
{
semaphore.Release();
}
});

await Task.WhenAll(tasks);
}
}
Без ограничения параллелизма множество задач будут выполняться одновременно, что может привести к большой нагрузке и снижению общей производительности. Вместо этого используйте SemaphoreSlim для управления количеством одновременных операций. Это отличный пример того, как повысить производительность приложения без ущерба для удобства чтения или сопровождения.

2. Используйте UseConfigureAwait(false), где возможно
Плохо:
public async Task<string> LoadDataAsync()
{
var data = await ReadDataAsync();
return ProcessData(data);
}

ConfigureAwait(false) — ценный приём, который может помочь предотвратить взаимоблокировки в асинхронном коде и повысить эффективность, не заставляя продолжения выполняться в исходном контексте синхронизации.
Хорошо:
public async Task<string> LoadDataAsync()
{
var data = await ReadDataAsync()
.ConfigureAwait(false);
return ProcessData(data);
}

Используйте ConfigureAwait(false) всегда в библиотечном коде и приложениях, не связанных с пользовательским интерфейсом.

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍14👎6
День 1595. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
III. Параллельные вычисления и библиотека параллельных задач (TPL)
Параллельные вычисления могут помочь использовать мощность многоядерных процессоров и ускорить операции, связанные с ЦП.

1. Используйте параллельные циклы с Parallel.For() и Parallel.ForEach()
Плохо:
private void ProcessData(List<int> data)
{
for (int i = 0; i < data.Count; i++)
{
PerformExpensiveOperation(data[i]);
}
}

Здесь для обработки данных используется стандартный цикл for, что приводит к последовательному выполнению операций. Это не позволяет использовать весь потенциал современных многоядерных процессоров.
Хорошо:
private void ProcessData(List<int> data)
{
Parallel.ForEach(
data, item =>
PerformExpensiveOperation(item));
}

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

2. Используйте класс Partitioner для эффективного распределения рабочей нагрузки.
Плохо:
private void ProcessData(IEnumerable<int> data)
{
Parallel.ForEach(data, item =>
PerformExpensiveOperation(item));
}

Здесь не уделяется особого внимания оптимизации разделения рабочей нагрузки между параллельными задачами. Это может привести к потенциальным накладным расходам и неравномерному распределению нагрузки.
Хорошо:
private void ProcessData(IEnumerable<int> data)
{
var partitioner = Partitioner.Create(data);
Parallel.ForEach(partitioner, item =>
PerformExpensiveOperation(item));
}

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

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

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍21
День 1596. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
IV. Важность кэширования данных
Кэширование может значительно повысить производительность приложений за счёт сокращения времени, затрачиваемого на выборку и обработку данных.

1. Реализовать кэширование данных с помощью кэша в памяти
Использование кэширования в памяти может значительно сократить время, требующее выборки данных из базы данных, и ускорить приложение.
Плохо:
public Product GetProductById(int id)
{
// Извлечение данных из БД каждый раз
var pr = _dbContext.Products
.FirstOrDefault(p => p.Id == id);
return pr;
}

Здесь данные о продукте извлекаются из базы каждый раз, когда вызывается метод. Это может привести к значительному снижению производительности, особенно если база данных расположена удалённо или находится под большой нагрузкой.
Хорошо:
private static MemoryCache _cache = 
new MemoryCache(new MemoryCacheOptions());

public Product GetProductById(int id)
{
// Извлечение из кэша, если возможно
if (!_cache.TryGetValue(id, out Product pr))
{
pr = _dbContext.Products
.FirstOrDefault(p => p.Id == id);
_cache.Set(id, pr, TimeSpan.FromMinutes(30));
}

return pr;
}

Использование кэширования в памяти для хранения данных сокращает затраты на выборку базы. Используйте MemoryCache для кэширования часто запрашиваемых данных и повышения производительности.

2. Реализуйте кэширование с помощью распределённых систем кэширования
Распределённые системы кэширования, такие как Redis, могут ещё больше повысить производительность вашего приложения за счёт кэширования данных способом, который масштабируется на нескольких серверах и обеспечивает высокую доступность. Например, используем распределённый кэш для извлечения списка популярных продуктов:
private static IDistributedCache _dCache;

public List<Product> GetProducts()
{
var key = "popularProducts";
var cached = _dCache.GetString(key);
if (cached == null)
{
var pr = _dbContext.Products
.Where(p => p.IsPopular).ToList();
_dCache.SetString(key,
JsonConvert.SerializeObject(pr),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(30)
});

return pr;
}
else
{
return JsonConvert
.DeserializeObject<List<Product>>(cached);
}
}

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

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍13