12 заметок с тегом

c#

Жизненный цикл запросов в ASP.NET Core MVC

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

Общий вид на жизненный цикл ASP.NET Core MVC приложений:

Он содержит в себе несколько этапов:

  • Middlewares
  • Routing
  • Controller Initialization
  • Controller action execution
  • Result Execution
  • View Rendering

Middlewares

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

В ASP.NET Core у нас есть 4 метода, с помощью которых мы можем создавать свои middlewares.

  • Use
  • Run
  • Map
  • MapWhen

Официальная документация

Routing

Маршрутизация позволяет найти для каждого URL подходящий обработчик, а также извлекает все параметры из URL и красиво их нам предоставляет. Для работы роутинга нам нужны две middleware:

  • UseRouting
  • UseEndpoints

Как все это работает?

UseRouting встраивает класс EndpointRoutingMiddleware, он смотрит на конечные точки, которые есть в приложении и выбирает подходящую. Выбор происходит на основании URL и заголовков. После того как нужный обработчик найден (Request delegate) его присваивают свойству Endpoint(IEndpointFeature) внутри текущего HttpContext. В дальнейшем мы можем получить его значения с помощью метода GetEndpoint.

UseEndpoints встраивает EndpointMiddleware. Он отвечает за выполнение установленной ранее конечной точки.

Официальная документация

Controller initialization

Для обеспечения работы контроллеров у есть два класса: ResourceInvoker, ControllerActionInvoker.

ResourceInvoker является абстрактным классом, который содержит общую логику работы с ресурсом, обработку исключений и результата.

ControllerActionInvoker наследует ResourceInvoker и добавляет в него логику работы с контроллерами и их методами.

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

Исходники классов можно посмотреть на github (ResourceInvoker, ControllerActionInvoker).

Action workflow

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

Общий вид этого жизненного цикла:

  • Фильтры авторизации
  • Фильтры ресурсов
  • Привязки моделей
  • Фильтры действий
  • Выполнение метода контроллера
  • Фильтры исключений
  • Фильтры результатов
  • Выполнение результата
  • Фильтры результатов

Детальнее про фильтры можно почитать на метаните.

Result execution

Методы контроллера возвращают объекты результата, которые в дальнейшем преобразовываются в соответствующее представление. Объект результата должен наследовать абстрактный класс ActionResult. Фреймворк предоставляет нам большое множество готовых классов.

Общий вид того как происходит рендеринг представлений:

Источники

 Нет комментариев    326   3 мес   .net   asp.net core   c#

Асинхронное программирование в C#

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

Что такое асинхронность?

Асинхронность — выполнение программного кода, не блокирующее потоки во время ожидания.

Например: делаем запрос в бд для получения некоторых данных.
У нас есть поток и запрос, который выполняет этот поток. При синхронном выполнении наш поток будет ждать, пока БД не ответит. В асинхронном случае наш поток будет выполнять другой запрос, пока БД обрабатывает текущий.

TAP

С приходом .NET Framework 4.0 появился новый способ для работы с асинхронностью — TAP. По сути, это набор классов, которые предоставляют более удобный интерфейс для работы с асинхронным кодом. Но мы всё ещё можем использовать старые подходы: APM и EAP. Но Майкрософт советует использовать TAP.

Центральным классов в TAP является Task. Он описывает отдельную задачу, выполнение которой может завершиться в какой-то момент. 

Детальнее про подходы работы с многопоточностью.

Как создать задачу?

Фреймворк предоставляет множество способов для создания и запуска задач, а также оборачивания старого кода (APM и EAP).

Фабрики запущенных задач

Task.Run(Action/Func)
Task.Factory.StartNew(Action/Func)

Фабрики завершенных задач

Task.FromResult(Result)
Task.FromCanceled(Result)
Task.FromException(Result)
Task.CompletedTask

Конструктор

var task = new Task(Action/Func)
task.Start()

Фабрики для оборачивания в таски

Task.Factory.FromAsync(APM pattern)
TaskCompletionSource(EAP, APM, etc)

Класс TaskScheduler

Класс, который содержит стратегию запуска и планирования задач Task. Существуют две стандартные реализации: ThreadPoolTaskScheduler и SynchronizationContextTaskScheduler. Первая запускает задачи с помощью пула потоков, а вторая — использует текущий контекст синхронизации.

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

У каждого потока есть свой «текущий» контекст, который необязательно должен быть уникальным. Иногда нам нужно, чтобы завершение или продолжение асинхронной операции выполнялись в том же контексте, откуда было запущено. Например, приложения с UI. После нажатия на кнопку мы запускаем асинхронную операцию, результат которой нужно вывести на экран. Но мы не сможем получить доступ к UI элементу, так как он находится в другом потоке. Для этого нам нужно запустить обновление данных в контексте UI потока. Именно для этого и нужен контекст синхронизации.

Контекст синхронизации при работе с Task и Thread

Комбинирование задач

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

Task.WaitAll();
Task.WaitAny();
Task.WhenAll();
Task.WhenAny();

Но нужно быть внимательным, так как WaitAll и WaitAny являются блокирующими операциями. Вместо них желательно использовать WhenAll и WhenAny.

Как получить результат?

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

Блокирующее получение результата:

.Result
.GetAwaiter().GetResult() - //лучше не использовать так как этот метод предназначен для компилятора, а не для нас.
.Wait()

Асинхронное:

await

Что такое sync over async deadlock?

Продолжение задач

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

Пример:

var task = Task.Run(() => Console.WriteLine("Async task"));
var nextTask = task.ContinueWith((prevTask) => {
	Console.WriteLine("Continuation");
});
nextTask.ContinueWith((prevTask) => {
	Console.WriteLine("Last Continuation");
});

Мы можем настраивать условия продолжения используя класс TaskContinuationOptions. С его помощью мы можем строить дерево задач, запускать продолжения по условиям и т. д.

Официальная документация

Обработка исключений

Работа с исключениями в асинхронном коде отличается от синхронного. Мы можем и не узнать об исключении, если не будем явно его обрабатывать. А в более старых версиях .NET это могло привести к падению приложения.

Обработка исключения при использовании await:

try
{
	await Task.Run(() => { throw new Exception(); });
}
catch (Exception e)
{
	// 
}

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

Task.Run(() => { throw new Exception(); })
	.ContinueWith((prev) => {
		if (prev.IsFaulted)
		{
			//
		}
	});

Task.Run(() => { throw new Exception(); })
	.ContinueWith((prev) => {
		
	}, TaskCreationOptions.OnlyOnfaulted);

Стоит избегать асинхронных методов, которые ничего не возвращают (async void). Так как при возникновении исключения внутри метода, мы просто не сможем его обработать.

Комбинаторы задач также по-разному предоставляют исключения:

  • Task.WaitAll() — вернёт AggregateException;
  • Task.WhenAll() — вернёт только первое исключение;

Отмена задач

Если нам нужно отменить выполнение асинхронной задачи мы можем воспользоваться классом CancellationToken. Про него я рассказывал раньше.

async/await

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

Что делает async?

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

Что делает await?

  • Позволяет не блокирующее ожидать результат.
  • Запуск продолжения в нужном потоке.
  • Возвращает результат или исключение.

Заблуждения по поводу await

  • Запускает операцию асинхронно.
  • Являться синтаксическим сахаром над Task.ContinueWtih.
  • Обязательно запускает продолжение в новый поток.
  • Всегда работает асинхронно.

Советы

  • Не используйте комбинацию async void — вы не сможете отловить исключение, а также ожидать выполнение метода.
  • Методы помеченные как async должны внутри себя содержать await.
  • Старайтесь вежде использовать не блокирующее ожидание await вместо блокирующего.
  • Для выполнения продолжительных операций используйте Task.Factory.StartNew вместе с TaskCreationOptions.LognRunning.
  • Используйте await t.ConfigureAwait(false) для библиотечного кода.
  • Не используйте совместно await и ContinueWith, так как они ничего не знают друг о друге.

Что осталось за кадром?

  • Асинхронные потоки
  • ValueTask
  • Progress

Ссылки

 Нет комментариев    894   6 мес   .net   c#

Лучшее объяснения работы семафора

A semaphore is like a nightclub: it has a certain capacity, enforced by a bouncer. Once it’s full, no more people can enter, and a queue builds up outside. Then, for each person that leaves, one person enters from the head of the queue. The constructor requires a minimum of two arguments: the number of places currently available in the nightclub and the club’s total capacity.

Алгоритм поиска Ли

Алгоритм поиска пути в планарном графе. Зачастую используется в схемотехнике и в играх (стратегиях) для поиска кратчайшего пути.

Алгоритм состоит из 3 шагов:

  1. Инициализация
  2. Распространение волны
  3. Восстановление пути

Также есть 2 способа поиска пути: ортогональный и ортогонально-диагональный. Ниже на скриншотах можно увидеть работу этих двух способов.

Ортогональный поиск.
Ортогонально-диагональный поиск.

Псевдокод

Взято из Википедии.

Инициализация

Пометить стартовую ячейку 
d := 0

Распространение волны

ЦИКЛ
  ДЛЯ каждой ячейки loc, помеченной числом d
    пометить все соседние свободные непомеченные ячейки числом d + 1
  КЦ
  d := d + 1
ПОКА (финишная ячейка не помечена) И (есть возможность распространения волны)

Восстановление пути

ЕСЛИ финишная ячейка помечена
ТО
  перейти в финишную ячейку
  ЦИКЛ
    выбрать среди соседних ячейку, помеченную числом на 1 меньше числа в текущей ячейке
    перейти в выбранную ячейку и добавить её к пути
  ПОКА текущая ячейка — не стартовая
  ВОЗВРАТ путь найден
ИНАЧЕ
  ВОЗВРАТ путь не найден

Пример кода, который реализует алгоритм и выводит на экран путь (C#).

Правила использования AutoMapper в .NET

Оригинал статьи: https://bool.dev/blog/detail/pravila-ispolzovaniya-automapper-v-net

Конфигурация
✔ Используйте инициализировать AutoMapper один раз в Mapper.Initialize в AppDomain startup в устаревшем ASP.NET
✔ Используйте использовать пакет AutoMapper.Extensions.Microsoft.DependencyInjection в ASP.NET Core с services.AddAutoMapper(assembly[])
✔ Используйте конфигурацию маппингов в профайле
✔ Рассмотрите организацию классов профайлов близко к типам назначения, которые они настраивают (иными словами в UserProfile должны быть маппинги классов связанных с юзером)
✔ Рассмотрите использование параметров конфигурации поддерживаемых LINQ, по сравнению с параметрами, не поддерживаемыми LINQ
Х Избегайте before/after map конфигурацию
Х Не размещайте логику которая строго не относится к поведению при маппинге данных
Х Не используйте MapFrom, когда элемент и так может быть автоматически смаплен
Х Не используйте AutoMapper, за исключением случаев, когда тип назначения представляет собой уплощенное подмножество свойств типа источника
Х Не используйте AutoMapper для поддержки сложной многоуровневой архитектуры
Х Избегайте использования AutoMapper, когда у вас значительный процент конфигурации прописан в форме Ignore или MapFrom
Х Не используйте доступ к статичному Mapper классу внутри профайла
Х Не используйте DI контейнер для регистрации всех профилей
Х Не используйте инджект зависимостей в профайлы
Х Не используйте CreateMap на каждый запрос
Х Не используйте inline maps

Моделирование
✔ Используйте не большие DTO
✔ Необходимо создавать внутренние типы в DTO для типов элементов, которые не могут быть сведены
✔ Следует помещать общие простые «computed property» в source модель
✔ Следует помещать «computed property» которые специфичны для dest модели в dest модель
Х Избегайте совмесное использование DTO между маппингами
Х Не создавайте DTO с «circular» ассоциациями
Х Избегайте изменения имен членов DTO для управления сереализацией

Выполнение
✔ Рассмотрите возможность использования query projection поверх in-memory mapping’a​
✔ Необходимо использовать mapping опции для runtime-resolved значений в проекциях.
✔ Используйте опции mappinga для разрешения контекстуализированных сервисов в in-memory mapping’e
Х Не абстрагируйте и не инкапсулируйте mapping за интерфейсом

Коллекции в .NET

Сегодня мы рассмотрим следующие интерфейсы:

  • IEnumerable
  • IQueryable
  • ICollection
  • IList
  • ISet
  • IDictionary
  • Queue и Stack

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

IEnumerable

Данный интерфейс является базовым для всех коллекций. В нем объявлен только один метод, который позволяет обходить элементы коллекции в цикле. Нельзя изменить данные из IEnumerable.

IQueryable

Всякий раз, когда мы сталкиваемся с большим количеством данных необходимо подумать, какую коллекцию или какой тип использовать для работы с ними. В отличии от IEnumerable — IQueryable предлагает высокую производительность в случае работы с большим объемом данных. IQueryable предварительно фильтрует данные по запросу а затем отправляет только отфильтрованные данные клиенту.

Разница между IQueryable и IEnumerable

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

Когда что использовать?

IEnumerable IQueryable
Может двигаться только вперед по коллекции, он не может идти назад может двигаться только вперед по коллекции, он не может идти назад
Хорошо подходит для работы с данными в памяти (списки, массивы) Лучше работает с запросами к базе данных (вне памяти)
Подходит для LINQ to Object и LINQ to XML Подходит для LINQ to SQL
Поддерживает отложенное выполнение (Lazy Evaluation) Поддерживает отложенное выполнение (Lazy Evaluation)
Не поддерживает произвольные запросы Поддерживает произвольные запросы (используя CreateQuery и метод Execute)
Не поддерживает ленивую загрузку (lazy loading) Поддерживает ленивую загрузку (lazy loading)
Методы расширения, работающие с IEnumerable принимают функциональные объекты Методы расширения, работающие с IQueryable принимают объекты выражения (expression tree)

ICollection

Этот интерфейс содержит методы, которые позволяют манипулировать элементами коллекции. Также содержит свойство Count.

IList

Расширяет коллекции методами поиска по индексу и индексатором.

ISet

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

ISet<int> set = new HashSet<int> { 1, 2, 3, 4, 5 };
 
set.UnionWith(new[] { 5, 6 });              // set = { 1, 2, 3, 4, 5, 6 }
set.IntersectWith(new[] { 3, 4, 5, 6, 7 }); // set = { 3, 4, 5, 6 }
set.ExceptWith(new[] { 6, 7 });             // set = { 3, 4, 5 }
set.SymmetricExceptWith(new[] { 4, 5, 6 }); // set = { 3, 6 }

Остальные только выполняют проверки над коллекцией и возвращают логическое значение (bool):

ISet<int> set = new HashSet<int> { 1, 2, 3, 4, 5 };
 
var isSubset = set.IsSubsetOf(new[] { 1, 2, 3, 4, 5 }); // = true
var isProperSubset = set.IsProperSubsetOf(new[] { 1, 2, 3, 4, 5 }); // = false
var isSuperset = set.IsSupersetOf(new[] { 1, 2, 3, 4, 5 }); // = true
var isProperSuperset = set.IsProperSupersetOf(new[] { 1, 2, 3, 4, 5 }); // = false
var equals = set.SetEquals(new[] { 1, 2, 3, 4, 5 }); // = true
var overlaps = set.Overlaps(new[] { 5, 6 }); // = true

IDictionary

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

Реализуют его следующие классы:

  1. Dictionary — обычный словарь.
  2. SortedDictionary — отсортированный список, реализован как словарь. Быстрее вставляет и удаляет элементы.
  3. SortedList — отсортированный список, который основан на массиве. Использует меньше памяти, чем SortedDictionary.

Queue и Stack

Класс Queue реализует принцип FIFO (first in, first out) — первым пришел, первым вышел.
Класс Stack похож на класс Queue, но он реализует принцип LIFO (последний пришел, первый вышел). Единственный элемент, который непосредственно доступен в этой коллекции, — это тот, который был добавлен совсем недавно.

Потокобезопасность

Обычные generic классы в Base Class Library имеют один очень важный недостаток, если вам нужно использовать их в многопоточных приложениях: они не являются поточно-ориентированными или по крайней мере, не полностью потокобезопасные.

Base Class Library содержит класс ReaderWriterLockSlim, который можно использовать для реализации этой конкретной функции относительно простым способом. Содержит 2 метода:

  1. EnterReadLock и ExitReadLock
  2. EnterWriteLock и ExitWriteLock

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

Immutable Collections

Immutable (неизменяемые) коллекции не включены в библиотеку базовых классов. Чтобы использовать их в проекте должен быть установлен пакет NuGet System.Collections.Immutable.

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

Ссылки

  1. https://bool.dev/blog/detail/sravnenie-kollektsiy-v-net
  2. https://bool.dev/blog/detail/kak-pravilno-vybrat-net-kollektsiyu

Первый проект

Нашел в старой переписке скриншоты игры, которую я делал когда только изучал программирование на C#. Игру делал на Unity в сеттинге сталкера. Дальше одной локации и простенького управления не зашло. Жаль что видео не сохранились.

Это был 2014 год.

Также пробовал моделировать в 3ds Max.

Разработка на C# с помощью Visual Studio Code

Пару недель назад решил перейти на VS Code в качестве основной IDE для .NET приложений. Составил список плагинов, которые использую каждый день.

  1. C#
  2. vscode-solution-explorer — позволяет работать с файлами .sln. Добавляет для этого дополнительное меню.
  3. Visual Studio IntelliCode
  4. PowerShell — отличное расширение, которое полностью заменяет PowerShell ISE.
  5. GitLens — Git supercharged
  6. Code Spell Checker
  7. C# Extension
  8. .NET Core Test Explorer
  9. Bookmarks

CancellationToken в C#

В .NET есть механизм, который позволяет отменять асинхронные операции. Состоит он из 3 элементов:

  1. CancellationTokenSource — создает маркеры отмены, которые можно получить из свойства Token и обрабатывает запросы на отмену.
  2. CancellationToken — сам маркер, позволяет отслеживать состояние отмены.
  3. OperationCanceledException — исключение, которое должно генерироваться в случае отмены асинхронной операции.

Как это работает?

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

Пример

public static void Main() 
{
    CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
    CancellationToken token = cancelTokenSource.Token;
    
    var task = Task.Run(() => Task(cancelTokenSource.Token), cancelTokenSource.Token);

    cancelTokenSource.Cancel();
}

public static void Task(CancellationToken cancellationToken) 
{
    while (true) 
    {
        // делает полезную работу.

        cancellationToken.ThrowIfCancellationRequested();
    }
    
}

Как работать в токеном?

Токен содержит набор полезных методов и полей:

  1. Register() — позволяет зарегистрировать callback функцию.
  2. IsCancellationRequested — свойство, возвращает true, если операция отменена.
  3. ThrowIfCancellationRequested() — метод, бросает исключения, в случае отмены задачи.
  4. CanBeCanceled — если свойство возвращает false, то запроса на отмену гарантированно не будет. Такой токен можно получить с помощью статического свойства CancellationTokenSource.None или вызвав конструктор с параметром false.

UPD: Также мы можем использовать CancellationToken для отмены задачи спустя какое-то время. Для этого достаточно вызвать CancelAfter передав ему количество миллисекунд спустя которые надо отменить задачу.

Источники:

  1. Задачи и отмена в .Net — tips & tricks
  2. Отмена задач и параллельных операций. CancellationToken
 Нет комментариев    211   2019   .net   c#
Ранее Ctrl + ↓