.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
День шестьсот четырнадцатый. #ЗаметкиНаПолях
Внедрение Зависимостей в
ASP.NET Core. Начало (1/4)
ASP.NET Core активно использует внедрение зависимостей. Теорию я описывал в этом посте, а тут будет только практика.
По умолчанию в приложениях используется контейнер внедрения зависимостей (контейнер DI) от Microsoft, который несколько ограничен в функционале, по сравнению с другими, но подходит в большинстве случаев. Он находится в отдельной библиотеке в пространстве имён Microsoft.Extensions.DependencyInjection. В проект он обычно включается в составе метапакета Microsoft.AspNetCore.All.

Необходимые для внедрения интерфейсы и классы (также называемые сервисами) регистрируются в коллекции типа IServiceCollection. Чаще всего это происходит в методе ConfigureServices класса Startup. Во время работы приложения контейнер DI отвечает за обнаружение необходимого сервиса и создание его экземпляра с нужным сроком жизни.
public void ConfigureServices(IServiceCollection services) { … }
Параметр services на момент вызова метода будет инициализирован хостом. В отличие от регистрации промежуточного ПО в методе Configure, порядок регистрации сервисов чаще всего не имеет значения.

Что регистрировать в сервисах?
В первую очередь – зависимости. Просмотрите свой код и найдите использования ключевого слова new. Если ваш класс A создаёт экземпляр класса B, вызывает его методы и использует результат их работы, то класс A зависит от класса B.
class A {
public MethodA() {
var b = new B();
b.MethodB();
}
}
Класс B - кандидат на регистрацию в сервисах. В общем случае это делается в 3 этапа:
1. Выделяем интерфейс из класса B:
interface InterfaceB {
void MethodB();
}
class B : InterfaceB {…}

2. Добавляем интерфейс в параметры конструктора класса A:
class A {
private readonly InterfaceB _b;

public A(InterfaceB b) {
_b = b;
}
}

3. Регистрируем реализацию интерфейса в контейнере DI:
services.AddTransient<InterfaceB, B>();

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

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

Продолжение следует…

Источник:
https://app.pluralsight.com/library/courses/aspdotnet-core-dependency-injection/
День шестьсот пятнадцатый. #ЗаметкиНаПолях
Внедрение Зависимостей в
ASP.NET Core. Продолжение (2/4)
Начало

Время жизни сервисов
Контейнер DI отвечает за создание и уничтожение экземпляров сервисов. Доступно 3 варианта регистрации:
1. Transient – новый экземпляр сервиса создаётся каждый раз, когда он требуется. Каждый класс, зависящий от такого сервиса, получит отдельный экземпляр:
services.AddTransient<IEmailSender, EmailSender>();
- самый простой в работе: если срок жизни сервиса сложно определить, Transient сервис чаще всего будет наиболее подходящим;
- не требует потокобезопасности, т.к. не подходит для сохранения состояния;
- потенциально может неэффективно использовать память и вести к созданию и уничтожению множества объектов.

2. Singleton – экземпляр создаётся на всё время работы приложения. Один и тот же экземпляр сервиса будет использоваться во всех зависящих от него классах всё время:
services.AddSingleton<ILoggerFactory, LoggerFactory>();
- изменение состояния сервиса (если оно допускается) должно быть потокобезопасно;
- обычно более эффективен, чем Transient, т.к. создаётся только один объект, поэтому подходит для дорогих в создании объектов,
- может приводить к утечкам памяти при регистрации больших редко используемых объектов;
- подходит для объектов с общими функциями, не сохраняющими состояние (например, логгер).

3. Scoped – экземпляр сервиса создаётся на время обработки запроса к приложению. Зависимые объекты в течение обработки одного запроса получат один и тот же экземпляр. Например, в EntityFramework класс DbContext регистрируется как Scoped:
services.AddScoped<IOrderService, OrderService>();

Избегайте захвата сервисов
Сервис не должен зависеть от другого сервиса с меньшим временем жизни! Если Singleton-сервис зависит от Transient- или Scoped-сервиса, то последний может быть захвачен и не будет уничтожен в течение всего времени жизни приложения. Это может приводить как к утечкам памяти, так и к совместному использованию непотокобезопасного кода из Transient-сервисов.
Начиная с ASP.NET Core 2.0 по умолчанию включена валидация времени жизни сервисов (ValidateScopes). Она приводит к ошибке времени выполнения при старте приложения, если будет обнаружен захват сервисов. Однако, учтите, что эта функция отключена в продакшн, и её не рекомендуется включать вне среды разработки.

Регистрация открытых обобщённых типов
Иногда требуется зарегистрировать реализацию обобщённого интерфейса. Например, ILogger<T>. В этом случае используется необобщённый метод Add… и typeof:
services.AddSingleton(
typeof(ILogger<>), typeof(MyLogger<>));
Если параметров типа больше одного, внутрь угловых скобок добавляются запятые(например, typeof(ISomeInterface<,>)).

Дескрипторы сервисов
Дескрипторы сервисов (класс ServiceDescriptor) содержат информацию о регистрируемых сервисах и используются внутри контейнера DI.
var sd = new ServiceDescriptor(typeof(IOrderService),
typeof(OrderService), ServiceLifetime.Scoped);
services.Add(sd);
Чаще всего создание дескриптора скрыто в методах расширения, описанных выше, и создание его вручную не требуется.

Продолжение следует…

Источник:
https://app.pluralsight.com/library/courses/aspdotnet-core-dependency-injection/
День шестьсот шестнадцатый. #ЗаметкиНаПолях
Внедрение Зависимостей в
ASP.NET Core. Продолжение (3/4)
Начало
Продолжение

Регистрация нескольких сервисов
Если в методе ConfigureServices используется несколько регистраций для одного интерфейса, то последняя из них имеет приоритет.
services.AddTransient<IMessageSender, EmailSender>();
services.AddTransient<IMessageSender, SMSSender>();
Здесь в приложении будет использован SMSSender.

Чтобы избежать случайной «перезаписи» зарегистрированных сервисов, можно использовать соответствующий TryAdd… метод (например, TryAddTransient). Тогда реализация будет добавлена, только если она ещё не была зарегистрирована ранее.
Важно отметить, что без использования TryAdd… зарегистрированы будут оба сервиса, но лишь последняя реализация будет использована при обнаружении сервиса.

Иногда нужно явно перезаписать предыдущую регистрацию. Тогда используется метод Replace, принимающий дескриптор сервиса:
services.Replace(
new ServiceDescriptor(typeof(IMessageSender),
typeof(SMSSender), ServiceLifetime.Transient)
);
Чтобы удалить все регистрации сервисов для определённого интерфейса, используется метод RemoveAll:
services.RemoveAll<IMessageSender>();

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

Зачем же контейнер DI регистрирует несколько реализаций? Это может пригодиться, когда вам нужно, например, зарегистрировать несколько отправителей сообщений, валидаторов данных или бизнес-правил, которые должны будут применены одно за другим. В этом случае в конструктор зависимого класса внедряется коллекция сервисов:
public class Notifier {
IEnumerable<IMessageSender> _senders;

public Notifier(
IEnumerable<IMessageSender> senders) {
_senders = senders;
}
public void SendMessage() {
foreach (var s in _senders) {
// обработка каждого отправителя
}
}
}

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

При регистрации нескольких реализаций может быть нежелательно регистрировать одинаковые реализации, что обычными методами не проверяется. В нашем случае, если случайно зарегистрировать SMSSender дважды, сообщение по смс будет отправлено 2 раза. Тогда можно использовать метод TryAddEnumerable, принимающий коллекцию дескрипторов сервисов:
services.TryAddEnumerable(new[]
{
new ServiceDescriptor(typeof(IMessageSender),
typeof(EmailSender), ServiceLifetime.Transient),
new ServiceDescriptor(typeof(IMessageSender),
typeof(SMSSender), ServiceLifetime.Transient),
new ServiceDescriptor(typeof(IMessageSender),
typeof(SMSSender), ServiceLifetime.Transient)
});
В этом случае будет зарегистрирована только одна реализация SMSSender.

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

Источник:
https://app.pluralsight.com/library/courses/aspdotnet-core-dependency-injection/
День шестьсот семнадцатый. #ЗаметкиНаПолях
Внедрение Зависимостей в
ASP.NET Core. Окончание (4/4)
Начало
Продолжение 1
Продолжение 2

Чистый код
В более-менее большом приложении метод ConfigureServices разрастается довольно быстро, и в нём становится сложно ориентироваться. В этом случае поможет создание методов расширения для IServiceCollection. По соглашению имена таких методов начинаются с Add:
public static IServiceCollection AddCoreServices(
this IServiceCollection services) {
services.AddScoped<…>();
//…
return services;
}
Важно возвращать из метода набор сервисов, чтобы эти методы расширения можно было объединять в цепочки. Тогда метод ConfigureServices будет выглядеть компактнее и понятнее:
public void ConfigureServices(
IServiceCollection services) {
services.AddCoreServices()
.AddDatabaseServices()
.AddCaching()

}

Для многих внутренних сервисов ASP.NET Core уже добавлены методы расширения, например, AddMvc(), AddIdentity(), AddDbContext() и т.п.

Механизмы обнаружения сервисов
В ASP.NET Core используется два основных механизма обнаружения. За обнаружение сервисов, зарегистрированных в контейнере DI, отвечает провайдер сервисов, реализующий IServiceProvider. Также используются статические методы класса ActivatorUtilities. В этом случае можно создавать сервисы, не зарегистрированные в контейнере DI, через конструктор. Конструктору можно передавать параметры либо напрямую, либо указать сервисы, зарегистрированные в конструкторе DI, и они будут обнаружены через провайдер сервисов. В ASP.NET Core таким образом создаются внутренние компоненты, вроде контроллеров, тег-хелперов, фильтров и т.п.

Другие типы внедрения зависимостей
1. В методы действия контроллеров
В ASP.NET Core помимо обычного внедрения зависимостей через конструктор доступно внедрение в методы действия контроллеров. Для этого используется атрибут параметра FromServices:
public async Task<IActionResult> Update(
[FromServices] IMessageSender sender) {…}
Это полезно, если сервис используется только в одном методе действия контроллера, и его не нужно создавать при вызовах других методов действия, как было бы при использовании внедрения через конструктор.

2. В промежуточное ПО (Middleware)
Проблема внедрения зависимостей в конструктор промежуточного ПО может быть при попытке внедрения Scoped сервиса, т.к. промежуточное ПО создаётся на всё время жизни приложения. В результате возникает ошибка захвата сервисов, описанная в предыдущем посте. В этом случае доступно внедрение зависимости прямо в метод Invoke/InvokeAsync промежуточного ПО. В конструктор промежуточного ПО можно внедрять только Singleton-сервисы.

3. В представления
Внедрять зависимости прямо в представления требуется редко, и это не рекомендуется, т.к. обычно это помещает бизнес логику в слой представления, где ей не место. Одним из сценариев использования может быть получение набора опций для выпадающего списка. В представлении используется ключевое слово inject
@inject IOptionsProvider options

Источник: https://app.pluralsight.com/library/courses/aspdotnet-core-dependency-injection/
День шестьсот восемнадцатый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
62. Парное Программирование и Состояние Потока
Представьте, что вы полностью поглощены тем, что вы делаете, сосредоточены, погружены в процесс. Возможно, вы потеряли счёт времени. Вы чувствуете себя счастливым. Вы поймали кураж. Вы в состоянии потока. Однако достичь и поддерживать состояние потока у всей команды разработчиков одновременно слишком трудно. Много вынужденных пауз взаимодействий между членами команды и других отвлекающих факторов, которые легко сбивают с ритма.

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

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

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

Существует множество ситуаций, когда состояние потока нарушается, а парное программирование помогает его сохранить:

1. Ослабление «фактора грузовика». Звучит цинично, но всё-таки: сколько и каких членов вашей команды придётся сбить грузовиком, чтобы команда не смогла успешно завершить проект? Другими словами, насколько ваша работа зависит от определённых членов команды? Являются ли их знания о проекте их личными или эти знания распределены между многими членами команды? Если вы ротируете задачи между парами, всегда будет кто-то, кто обладает нужными знаниями и сможет выполнить работу. В результате поток вашей команды менее подвержен "фактору грузовика".

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

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

4. Устойчивость к сбоям. Если что-то вас отвлекает: вам нужно ответить на телефонный звонок или email или поприсутствовать на встрече, - ваш партнёр по парному программированию может продолжать писать код. Когда вы вернётесь, ваш партнёр все еще будет в потоке, и вы быстро присоединитесь к нему.

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

В состоянии потока вы наиболее продуктивны, но оно очень уязвимо. Делайте всё возможное, чтобы поймать и удержаться в нём!

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Gudny Hauknes
День шестьсот девятнадцатый. #ЗаметкиНаПолях
Запуск Миграций EFCore из Вашего Кода
Все пользователи Entity Framework/EF Core знают, что запустить миграции из консоли или менеджера пакетов можно с помощью команды:
Update-Database
Кроме того, можно использовать команду dotnet ef:
dotnet ef database update

Но иногда может потребоваться запускать миграции по запросу из кода C#, например, вызвав конечную точку API из панели администратора сайта. Как правило, это было проблемой в более старых версиях Entity Framework, когда «версия» базы данных не совпадала с «версией» нужной EF Code. В этом случае «всё падало». В EF Core с этим меньше проблем, так как ваш код не рухнет, пока не попытается сделать что-то, что не может завершить (например, выбрать столбец, который ещё не существует). Но все же есть случаи, когда нужно развернуть код, протестировать его работу в промежуточной среде с действующей базой данных, а затем запустить миграцию базы данных по запросу.

Миграция EF Core в C#
На самом деле все очень просто.
var migrator = _context.Database.GetService<IMigrator>();
await migrator.MigrateAsync();
Где _context – это контекст вашей базы данных.

Проверка Ожидающих Миграций
Также может быть нужно проверить, есть ли ожидающие миграции, прежде чем пытаться их запустить. Например, может быть полезно узнать, в каком состоянии находится база данных, из панели администратора:
await _context.Database.GetPendingMigrationsAsync();

Миграция При Запуске Приложения
В некоторых случаях вам всё равно, когда выполняется миграция, вы просто хотите, перенести базу данных до запуска приложения. Это подойдёт для проектов, в которых время миграции базы данных не имеет значения или оно относительно небольшое. Например, для мало нагруженных веб-сайтов, расположенных на одном сервере. Для этого в .NET Core есть парадигма «Фильтров Запуска» (StartupFilter). Код будет примерно следующим:
public class MigrationStartupFilter<TContext> : 
IStartupFilter where TContext : DbContext {
public Action<IApplicationBuilder>
Configure(Action<IApplicationBuilder> next) {
return app => {
using (var scope =
app.ApplicationServices.CreateScope()) {
foreach (var context in
scope.ServiceProvider.GetServices<TContext>()) {
context.Database.SetCommandTimeout(160);
context.Database.Migrate();
}
}
next(app);
};
}
}
Фильтры запуска в .NET Core в основном похожи на фильтры в MVC. Они встраиваются в процесс запуска и выполняют код перед запуском приложения, причём только при запуске. Если вы когда-либо использовали Application_Start() в global.asax в .NET Framework, то это очень похоже.

Осталось добавить наш фильтр в контейнер DI в Startup.cs:
services.AddTransient<IStartupFilter, 
MigrationStartupFilter<Context>>();
Здесь Context - это контекст нашей базы данных.

Источник: https://dotnetcoretutorials.com/
День шестьсот двадцатый. #Оффтоп #Курсы
Сегодня расскажу вам о нескольких предстоящих онлайн событиях, которые могут быть интересны разработчикам .NET. Пойдём в хронологическом порядке.

Решения SAP в Microsoft Azure
13 октября 2020г., начало в 10:00 (мск.)
Язык: русский
Темой вебинара станет переход на SAP S/4HANA в Microsoft Azure, который мгновенно обеспечивает компаниям гибкость и возможности для получения полезных сведений. Кроме того, такой переход упрощает будущее развитие инноваций и усовершенствование бизнес-процессов. Представители Microsoft и SAP расскажут, каким образом эти компании использовали SAP S/4HANA в Azure для трансформации собственных бизнес-процессов. Вы познакомитесь со всеми инфраструктурными предложениями от Microsoft Azure, которые базируются на новейших технологиях Intel и разработаны специально для SAP S/4HANA.

Pluralsight LIVE
13 октября 2020г., начало в 22:00 (мск.)
Язык: английский
Pluralsight LIVE - ежегодная конференция по технологическим навыкам, которая в этом году будет виртуальной. Microsoft представляет в этом году углублённое занятие, посвященное развитию навыков работы с Azure (Build your Azure skills with Microsoft Learn and Pluralsight). На этом занятии вы узнаете, как получить навыки, необходимые для решения задач современного облачного цифрового мира. Вы узнаете:
- что нового в Azure,
- как приобретение необходимых навыков в Azure может вам помочь использовать новейшие технологии,
- как работает совместное предложение от Microsoft Learn и Pluralsight,
- какие сертификаты подходят вам или вашей команде.

Основы проектирования защищённых облачных решений в Azure
16 октября 2020г., начало в 10:00 (мск.)
Язык: русский
Аспекты, связанные с безопасностью, нужно учитывать на протяжении всего жизненного цикла приложения — от разработки и реализации до развертывания и эксплуатации. Используйте подходы, описанные в Azure Well-Architected Framework для построения безопасных решений, способных противостоять современным киберугрозам.
В ходе этого вебинара вы узнаете об:
- Общих принципах проектирования защищённой облачной архитектуры.
- Ключевых рисках и требованиях безопасности.
- Подходах к проектированию защищённых систем управления удостоверениями и доступом, сетей, хранилищ и баз данных, приложений и служб.
- Практиках управления безопасностью.

State of .NET - .NET 5 Preview
28 октября 2020г.
Язык: английский
Очередной эпизод из серии вебинаров «State of .NET» от CODE Magazine. В нём расскажут о том, чего ожидать от предстоящего релиза .NET 5.

PS: Все вебинары бесплатны.
PPS: Pluralsight объявили неделю бесплатных курсов для новых подписчиков с 12 по 18 октября! Налетай!
День шестьсот двадцать первый. #BestPractices
6 Советов по Работе с Памятью в Приложениях .NET. Начало 1-2
Проблемы с памятью в большом .NET-приложении - своего рода тихий убийца. Как гипертония. Вы можете долго есть нездоровую пищу, игнорируя её, пока однажды не столкнетесь с серьезной проблемой. В случае программы .NET серьезной проблемой может быть высокое потребление памяти, проблемы с производительностью и сбои в работе.

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

1. Объекты должны собираться сборщиком мусора как можно раньше
Чтобы ваша программа работала быстро, главная цель - как можно раньше уничтожать объекты. Чтобы понять, почему это важно, нужно иметь понятие о поколениях объектов в .NET. https://t.iss.one/NetDeveloperDiary/208 Когда объекты создаются с помощью new, они помещаются в куче в поколении 0. Это очень маленький объем памяти. Если после сборки мусора на них по-прежнему есть ссылки, они переводятся в поколение 1. Поколение 1 - это больший объем памяти. Если после следующей сборки мусора на них по-прежнему ссылаются, они переводятся в поколение 2.

Сборки мусора в поколении 0 производятся наиболее часто и очень быстро. Сборка в поколении 1 охватывает как пространства памяти как поколения 0, так и поколения 1, поэтому она более дорогая. Сборка в поколении 2 включает в себя всё пространство памяти, включая кучу больших объектов (LOH). Сборка мусора в поколении 2 очень дорогая. Сборщик мусора (GC) оптимизирован, чтобы проводить много сборок в поколении 0, меньше в поколении 1 и очень мало сборок в поколении 2. Но, если у вас много объектов, которые переходят в более высокое поколение, вы получите обратный эффект. Это приводит к нехватке памяти (также известной как давление на GC) и снижению производительности.

Кстати, размещение новых объектов обходится крайне дешево. Единственное, о чем нужно беспокоиться, - это сборка мусора.

Так как же держать объекты в младшем поколении? Просто убедитесь, что ссылки на них удаляются как можно раньше. Некоторые объекты, например одиночки, должны всегда оставаться в памяти. Ничего страшного, обычно это будут службы, которые в любом случае не потребляют много памяти.

2. Используйте кэширование… но осторожно
Такие механизмы, как кэширование, по определению проблемные. Это долговечные временные объекты, которые, скорее всего, будут переведены в поколение 2, хотя это плохо для сборщика мусора, но обычно стоит того, потому что кэширование действительно может улучшить производительность. Однако нужно следить за ним.

Один из способов уменьшить это давление на память - использовать изменяемые объекты кэша. Это означает, что вместо замены объекта кэша вы обновляете существующий объект. То есть у сборщика мусора будет меньше работы по продвижению объектов между поколениями и больше сборок мусора в поколениях 0 и 1.

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

Продолжение следует…

Источник:
https://michaelscodingspot.com/application-memory-health/
День шестьсот двадцать второй. #BestPractices
6 Советов по Работе с Памятью в Приложениях .NET. Продолжение 3-4
Начало

3. Следите за процентом времени, затрачиваемым на сборку мусора
Если вы хотите узнать, насколько сборка мусора влияет на время выполнения программы, это довольно легко сделать. Просто посмотрите на счётчик производительности (Performance Monitor) .NET CLR Memory | % Time in GC. Он покажет, какой процент времени выполнения используется сборщиком мусора. Есть много инструментов для просмотра счетчиков производительности. В Windows вы можете использовать PerfMon. В Linux вы можете использовать dotnet-trace.

Далее приведены пара «магических чисел», но относитесь к ним с подозрением, потому что у каждого приложения свой контекст. Для большого приложения 10% времени в GC – это, скорее всего, нормальный процент. 20% времени в сборке мусора - это предел, а большее число означает, что у вас проблемы.

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

Нельзя точно сказать, сколько сборок в поколении 2 у вас должно быть. Просто следите за их количеством, и, если этот показатель возрастает, вероятно, вы добавили какое-то нехорошее поведение. Вы можете посмотреть это число с помощью счётчика производительности .NET CLR Memory | % Gen 2 Collections (см. картинку ниже).

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

Источник:
https://michaelscodingspot.com/application-memory-health/
День шестьсот двадцать третий. #BestPractices
6 Советов по Работе с Памятью в Приложениях .NET. Окончание 5-6
Начало
Продолжение

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

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

Если операция вызывает утечку объектов, каждая такая операция увеличивает потребление памяти. Когда проходит достаточно времени, потребление памяти приближается к своему пределу. В 32-битном процессе этот предел составляет 4 ГБ. В 64-битном процессе это зависит от ограничений компьютера. Когда потребление памяти приближаемся к пределу, сборщик мусора паникует. Он начинает запускать полную сборку мусора при каждом выделении памяти, чтобы не исчерпать её совсем. Это может замедлить ваше приложение до скорости улитки. Однако в итоге потребление памяти таки достигает предела, и приложение аварийно завершает работу, выбрасывая OutOfMemoryException.

Чтобы убедиться, что с вашим приложением такого не случится, активно отслеживайте потребление памяти с течением времени. Лучший способ сделать это - посмотреть на счетчик производительности Process | Private Bytes. Вы можете легко сделать это с помощью Process Explorer или PerfMon.

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

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

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

Лучший способ обнаружить и исправить утечки памяти - использовать профилировщик памяти.

Источник: https://michaelscodingspot.com/application-memory-health/
День шестьсот двадцать четвёртый. #Книги
Несколько человек спрашивали в чате и в личке, какие книжки почитать, поэтому сделаю тут небольшую подборку.

Следующие книги распространяются свободно в PDF.
1. "Learn Azure in a Month of Lunches"
Быстрый старт в Azure в 21 уроке. В этой книге более 350 страниц, но лишь краткое описание всего того, что вы можете делать в Azure. В ней выборочно представлено только самое главное из того, чем вам может помочь Azure при создании приложений. Книга не охватывает все из более сотни сервисов Azure и не дает исчерпывающих подробностей о сервисах, которые включены в неё. Вместо этого она фокусируется на основных моментах некоторых основных сервисов и показывает примеры, как их безопасно соединить, а также знакомит с возможностями того, что вы можете создать в Azure.

Далее пара книг, которые представляют собой скомпилированную документацию Майкрософт:
2. "Architecting Modern Web Applications with ASP.NET Core and Azure"
В этом руководстве представлены комплексные рекомендации по созданию монолитных веб-приложений с использованием ASP.NET Core и Azure. Здесь понятие «монолитный» означает, что эти приложения развёртываются как единое целое, а не как набор взаимодействующих сервисов и приложений.

3. ".NET Microservices. Architecture for Containerized .NET Applications"
В противовес предыдущей книге, это введение в разработку приложений на основе микросервисов и управление ими с помощью контейнеров. Здесь обсуждаются подходы к архитектурному проектированию и реализации с использованием контейнеров .NET Core и Docker.

Ещё несколько недавно вышедших книг, которые советовали в чате. Их в PDF выкладывать не буду, чтобы не пиратить, мы тут люди приличные))) А, если очень хочется, гуглить все умеют, надеюсь ;)
4. Jason Alls "Clean Code in C#", Packt Publishing, 2020
Советы по рефакторингу легаси кода C# и улучшению производительности приложения применяя передовые практики. Вы узнаете, как определить проблемный код, который не обеспечивает читабельность, поддерживаемость и расширяемость. А также о различных инструментах, паттернах и способах рефакторинга кода, чтобы сделать его чистым.

5. Stephen Cleary "Concurrency in C# Cookbook: Asynchronous, Parallel, and Multithreaded Programming" 2nd Edition. O’Reilly, 2019.
Параллелизм - ключевой аспект красивого программного обеспечения. На протяжении десятилетий параллелизм был возможен, но труден: параллельное ПО было сложно писать, трудно отлаживать и поддерживать. В результате многие разработчики выбирали более простой путь и избегали параллелизма. Однако с библиотеками и языковыми функциями, доступными для современных программ .NET, параллелизм стал намного проще. Раньше параллельное программирование было прерогативой экспертов, в наши дни каждый разработчик может (и должен) использовать параллелизм.
Если_бы_программисты_строили_дома.pdf
96.6 KB
День шестьсот двадцать пятый. #Юмор
Олды тут? Вон чего откопал в старых документах. Помню, дико популярны были такие «Хроники» в начале 2000х. Решил молодость вспомнить)))

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

https://telegra.ph/Esli-by-programmisty-stroili-doma-10-16
День шестьсот двадцать шестой. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
63. Используйте Типы Специфичные для Домена Вместо Примитивных
23 сентября 1999г. Mars Climate Orbiter стоимостью 327,6 млн долларов был потерян при выходе на орбиту Марса из-за ошибки в программном обеспечении на Земле. Позднее выяснили, что перепутали единицы измерения. Программное обеспечение на Земле, поставленное компанией Локхид-Мартин, подавало команды аппарату, используя британские единицы силы фунты, в то время как космический корабль, сделанный в НАСА, ожидал принятые в системе СИ ньютоны. Из-за этого наземная станция недооценила мощность двигателей космического корабля почти в 4,5 раза. Эта неудача считается одной из причин окончательного и полного перехода NASA на метрическую систему, объявленного в 2007 году.

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

Если VelocityInKnots представляет собой Float в диапазоне от 0.0 до 500.00,
а DistanceInNauticalMiles - Float от 0.0 до 3000.00, то следующий код:
SomeNumber : Float;
SomeNumber := DistanceInNauticalMiles + VelocityInKnots;
приведёт к ошибке компиляции.

Разработчики, работающие в менее требовательных к безопасности доменах, также могут извлечь выгоду из применения более специфичной для предметной области типизации, где в противном случае они могли бы продолжать использовать примитивные типы данных, такие как строки и числа с плавающей точкой. В Java, C++, C#, Python и других современных языках абстрактный тип данных известен как класс. Использование таких классов, как VelocityInKnots и DistanceInNauticalMiles, серьёзно повысит качество кода:
- Код становится более читабельным, поскольку он выражает намерения в терминах домена, а не в виде общих Float или String.
- Код становится более тестируемым, поскольку легче тестировать инкапсулированное поведение.
- Упрощается повторное использование в приложениях и системах.

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

Мораль в том, что для повышения качества вашего программного обеспечения стоит использовать типы, специфичные для предметной области.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Einar Landre
День шестьсот двадцать седьмой. #Оффтоп
Вот вам тема для воскресного обсуждения: «Как получить повышение в должности разработчика?»

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

1. Узнайте правила или критерии для повышения
Многие разработчики тратят огромные усилия, выполняя (и хорошо выполняя) работу, которая не расценивается в компании, как критерий для повышения. Если вы исправляете множество мелких недочётов в ПО, вы можете считать, что по количеству исправленных тикетов вы чемпион, но в компании могут цениться совсем другие качества. В гигантах, вроде FAGMA, есть чёткая градация уровней и список требований к квалификации на каждом уровне. В небольших компаниях вы можете прямо спросить об этом тимлида, начальника отдела или коллегу, которого недавно повысили.

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

3. Ищите возможности
Чем выше должность в иерархии, тем объёмнее и серьёзнее задачи, которые выполняет специалист. Но и количество таких задач меньше. Как же их заполучить? Посещайте собрания, говорите с коллегами и менеджерами, предлагайте свои идеи. Проще говоря, повышайте софт скилы.

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

5. Дайте знать окружающим о своей работе
Этого разработчики обычно не любят, но без этого никуда. Ваши коллеги, а, главное, ваши начальники должны знать о том, что вы делаете и какие усилия вы прикладываете. Многие путают это с офисными интригами, подсиживанием коллег и присваиванием чужих результатов. Нет, не будьте такими. Здесь речь идёт исключительно о ваших заслугах и вашем вкладе в общее дело. Убедитесь, что ваши начальники в курсе. А это означает, что нужно поддерживать с ними связь и хорошие отношения. Снова софт скиллз.

6. Усердно работайте
«Спасибо, кэп», - скажете вы. Но факт остаётся фактом. Повышение – это не только «лычка» и больше денег, но и больше работы и ответственности. Повышения «за выслугу лет», конечно, случаются, но это скорее исключения.

В комментариях предлагаю вам описать свой опыт. Как вам удалось добиться повышения? Это было в крупной или небольшой компании? Какими советами вы можете поделиться?

Источник: https://www.youtube.com/watch?v=vm4OsDPK1gA
День шестьсот двадцать восьмой. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Начало 1-4
1. Лишний async/await
Использование ключевых слов async/await приводит к неявному выделению памяти под конечный автомат, который отвечает за организацию асинхронных вызовов. Когда ожидаемое выражение является единственным или последним оператором в функции, его можно пропустить. Однако с этим есть несколько проблем, о которых вам следует знать.
Неверно
async Task DoSomethingAsync(){
await Task.Yield();
}
Верно
Task DoSomethingAsync() {
return Task.Yield();
}

2. Вызов синхронного метода внутри асинхронного метода
Если мы используем асинхронный код, мы всегда должны использовать асинхронные методы, если они существуют. Многие API предлагают асинхронные аналоги своих хорошо известных синхронных методов.
Неверно
async Task DoAsync(Stream file, byte[] buffer) {
file.Read(buffer, 0, 10);
}
Верно
async Task DoAsync(Stream file, byte[] buffer) {
await file.ReadAsync(buffer, 0, 10);
}

3. Использование async void
Есть две причины, по которым async void вреден:
- Вызывающий код не может ожидать метод async void.
- Невозможно обработать исключение, вызванное этим методом. Если возникает исключение, ваш процесс падает!
Вы всегда должны использовать async Task вместо async void, если только это не обработчик событий, но тогда вы должны гарантировать себе, что метод не выбросит исключения.
Неверно
async void DoSomethingAsync() {
await Task.Yield();
}
Верно
async Task DoSomethingAsync() {
await Task.Yield();
}

4. Неподдерживаемые асинхронные делегаты
Если передать асинхронную лямбда-функцию в качестве аргумента типа Action, компилятор сгенерирует метод async void, недостатки которого были описаны выше. Есть два решения этой проблемы:
- изменить тип параметра с Action на Func<Task>,
- реализовать этот делегат синхронно.
Стоит отметить, что некоторые API-интерфейсы уже предоставляют аналоги своих методов, которые принимают Func<Task>.
void DoSomething() {
Callback(async() => {
await Task.Yield();
});
}
Неверно
void Callback(Action action){…}
Верно
void Callback(Func<Task> action){…}

Продолжение следует…

Источник:
https://cezarypiatek.github.io/post/async-analyzers-p1/
👍1
День шестьсот двадцать девятый. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 5-7
Начало

5. Не ожидать Task в выражении using
System.Threading.Tasks.Task реализует интерфейс IDisposable. Вызов метода, возвращающего Task, непосредственно в выражении using приводит к удалению задачи в конце блока using, что никогда не является ожидаемым поведением.
Неверно
using (CreateDisposableAsync()) {

}
Верно
using (await CreateDisposableAsync()) {

}

6. Не ожидать Task в блоке using
Если мы пропустим ключевое слово await для асинхронной операции внутри блока using, то объект может быть удалён до завершения асинхронного вызова. Это приведёт к неправильному поведению и очень часто заканчивается исключением времени выполнения, сообщающим о том, что мы пытаемся работать с уже удалённым объектом.
Неверно
private Task<int> DoSomething() {
using (var service = CreateService()) {
return service.GetAsync();
}
}
Верно
private async Task<int> DoSomething() {
using (var service = CreateService()) {
return await service.GetAsync();
}
}

7. Не ожидать результата асинхронного метода
Отсутствие ключевого слова await перед асинхронной операцией приведет к завершению метода до завершения данной асинхронной операции. Метод будет вести себя непредсказуемо, и результат будет отличаться от ожиданий. Ещё хуже, если неожидаемое выражение выбросит исключение. Оно остается незамеченным и не вызовет сбоя процесса, что ещё больше затруднит его обнаружение. Вы всегда должны ожидать асинхронное выражение или присвоить возвращаемую задачу переменной и убедиться, что в итоге она где-то ожидается.
Неверно
async Task DoSomethingAsync() {
await DoSomethingAsync1();
DoSomethingAsync2();
await DoSomethingAsync3();
}
Верно
async Task DoSomethingAsync() {
await DoSomethingAsync1();
await DoSomethingAsync2();
await DoSomethingAsync3();
}

Продолжение следует…
Источник:
https://cezarypiatek.github.io/post/async-analyzers-p1/
День шестьсот тридцатый. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 8-10
Начало
Часть 2

8. Синхронные ожидания
Если вы хотите ожидать асинхронное выражение в синхронном методе, вы должны переписать всю цепочку вызовов на асинхронную. Кажется, что более легкое решение - вызвать Wait или Result для возвращаемой задачи. Но это будет стоить вам заблокированного потока и может привести к взаимной блокировке. Сделайте всю цепочку асинхронной. В консольном приложении допустим асинхронный метод Main, в контроллерах ASP.NET – асинхронные методы действия.
Неверно
void DoSomething() {
Thread.Sleep(1);
Task.Delay(2).Wait();
var result1 = GetAsync().Result;
var result2 = GetAsync().GetAwaiter().
GetResult();
var unAwaitedResult3 = GetAsync();
var result3 = unAwaitedResult3.Result;
}
Верно
async Task DoSomething() {
await Task.Delay(1);
await Task.Delay(2);
var result1 = await GetAsync();
var result2 = await GetAsync();
}

9. Отсутствие ConfigureAwait(bool)
По умолчанию, когда мы ожидаем асинхронную операцию, продолжение планируется с использованием захваченного SynchronizationContext или TaskScheduler. Это приводит к дополнительным затратам и может привести к взаимной блокировке, особенно в WindowsForms, WPF и старых приложениях ASP.NET. Вызывая ConfigureAwait(false), мы гарантируем, что текущий контекст игнорируется при вызове продолжения. Подробнее о ConfigureAwait в серии постов с тегом #AsyncAwaitFAQ
Неверно
async Task DoSomethingAsync() {
await DoSomethingElse();
}
Верно
async Task DoSomethingAsync() {
await DoSomethingElse().
ConfigureAwait(false);
}

10. Возврат null из метода, возвращающего Task
Возврат null значения из синхронного метода типа Task/Task<> приводит к исключению NullReferenceException, если кто-то ожидает этот метод. Чтобы этого избежать, всегда возвращайте результат этого метода с помощью помощников Task.CompletedTask или Task.FromResult<T>(null).
Неверно
Task DoAsync() {
return null;
}
Task<object> GetSomethingAsync() {
return null;
}
Task<HttpResponseMessage>
TryGetAsync(HttpClient httpClient) {
return httpClient?.
GetAsync("/some/endpoint");
}
Верно
Task DoAsync() {
return Task.CompletedTask;
}
Task<object> GetSomethingAsync() {
return Task.FromResult<object>(null);
}
Task<HttpResponseMessage>
TryGetAsync(HttpClient httpClient) {
return httpClient?.
GetAsync("/some/endpoint") ??
Task.FromResult(default(
HttpResponseMessage));
}

Продолжение следует…
Источник:
https://cezarypiatek.github.io/post/async-analyzers-p2/
День шестьсот тридцать первый. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 11-13
Начало
Часть 2
Часть 3

11. Передача токена отмены
Если вы забудете передать токен отмены, это может привести к большим проблемам. Запущенная длительная операция без токена отмены может заблокировать действие по остановке приложения или привести к нехватке потоков при большом количестве отменённых веб-запросов. Чтобы избежать этого, всегда создавайте и передавайте токен отмены методам, которые его принимают, даже если это необязательный параметр.
Неверно
public async Task<string>
GetSomething(HttpClient http) {
var response = await
http.GetAsync(new Uri("/endpoint/"));
return await
response.Content.ReadAsStringAsync();
}
Верно
public async Task<string> 
GetSomething(HttpClient http,
CancellationToken ct) {
var response = await
http.GetAsync(new Uri("/endpoint/"),
ct);
return await
response.Content.ReadAsStringAsync();
}

12. Использование токена отмены с IAsyncEnumerable
Эта ошибка похожа на предыдущую, но относится исключительно к IAsyncEnumerable и её довольно легко допустить. Это может быть неочевидно, но передача токена отмены в асинхронный перечислитель выполняется путем вызова метода WithCancellation().
Неверно
async Task Iterate(
IAsyncEnumerable<string> e) {
await foreach (var item in e) {

}
}
Верно
async Task Iterate(
IAsyncEnumerable<string> e,
CancellationToken ct) {
await foreach (var item in
e.WithCancellation(ct))
{

}
}

13. Имена асинхронных методов должны заканчиваться на Async
И наоборот, имена синхронных методов не должны заканчиваться на Async. Так обычно происходит при невнимательном рефакторинге. Это не строгое обязательство и не ошибка. Вообще, это соглашение об именовании имеет смысл только тогда, когда класс предоставляет как синхронные, так и асинхронные версии метода.
Неверно
async Task DoSomething() {
await Task.Yield();
}
Верно
async Task DoSomethingAsync() {
await Task.Yield();
}

Продолжение следует…
Источник:
https://cezarypiatek.github.io/post/async-analyzers-p2/
День шестьсот тридцать второй. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 14-16
Начало
Часть 2
Часть 3
Часть 4

14. Злоупотребление Task.Run в простых случаях
Для предварительно вычисленных или легко вычисляемых результатов нет необходимости вызывать Task.Run, который поставит задачу в очередь пула потоков, а задача немедленно завершится. Вместо этого используйте Task.FromResult, чтобы создать задачу, обёртывающую уже вычисленный результат.
Неверно
public Task<int> AddAsync(int a, int b) {
return Task.Run(() => a + b);
}
Верно
public Task<int> AddAsync(int a, int b) {
return Task.FromResult(a + b);
}

15. Await лучше, чем ContinueWith
Хотя методы продолжений по-прежнему можно использовать, обычно рекомендуется использовать async/await вместо ContinueWith. Кроме того, надо отметить, что ContinueWith не захватывает SynchronizationContext и поэтому семантически отличается от async/await.
Неверно
public Task<int> DoSomethingAsync() {
return CallDependencyAsync()
.ContinueWith(task => {
return task.Result + 1;
});
}
Верно
public async Task<int> DoSomethingAsync() {
var result =
await CallDependencyAsync();
return result + 1;
}

16. Синхронизация асинхронного кода в конструкторах
Конструкторы синхронны. Если вам нужно инициализировать некоторую логику, которая может быть асинхронной, использование Task.Result для синхронизации в конструкторе может привести к истощению пула потоков и взаимным блокировкам. Лучше использовать статическую фабрику.
public interface IRemoteConnFactory {
Task<IRemoteConn> ConnectAsync();
}
public interface IRemoteConn { … }
Неверно
public class Service : IService {
private readonly IRemoteConn _conn;

public Service(IRemoteConnFactory cf) {
_conn = cf.ConnectAsync().Result;
}
}
Верно
public class Service : IService {
private readonly IRemoteConn _conn;

private Service(IRemoteConn conn) {
_conn = conn;
}

public static async Task<Service>
CreateAsync(IRemoteConnFactory cf)
{
return new Service(
await cf.ConnectAsync());
}
}

var cf = new ConnFactory();
var srv = await Service.CreateAsync(cf);

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

Источник:
https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md