{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Богдан Стефанюк: дописи з тегом dotnet",
    "_rss_description": "Всім привіт! Я — Богдан, фулстек розробник з Києва. Пишу в основному про програмування та штуки які вивчаю. Час від часу публікую підбірки фоток, зроблених на плівку, розказую про подорожі та цікаві речі, що оточують мене.",
    "_rss_language": "uk",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": "",
    "_itunes_explicit": "",
    "home_page_url": "https:\/\/www.stefaniuk.website\/tags\/dotnet\/",
    "feed_url": "https:\/\/www.stefaniuk.website\/tags\/dotnet\/json\/",
    "icon": "https:\/\/www.stefaniuk.website\/pictures\/userpic\/userpic@2x.jpg?1565716580",
    "authors": [
        {
            "name": "Bohdan Stefaniuk",
            "url": "https:\/\/www.stefaniuk.website\/",
            "avatar": "https:\/\/www.stefaniuk.website\/pictures\/userpic\/userpic@2x.jpg?1565716580"
        }
    ],
    "items": [
        {
            "id": "271",
            "url": "https:\/\/www.stefaniuk.website\/all\/ordered-queue-and-heap\/",
            "title": "Очередь с приоритетом",
            "content_html": "<p>Представим что нужно спроектировать список задач. У каждой задачи есть приоритет, это значит, что вначале должны быть самые приоритетные задачи. Сами задачи можно обрабатывать только по одной, мы не можем взять задачу с центра списка.<\/p>\n<div style=\"max-width: 450px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-pic-1.png\" width=\"628\" height=\"240\" alt=\"\" \/>\n<\/div>\n<\/div><p>Такой список достаточно просто реализовать с помощью обычного массива. Задачи будем хранить в случайном порядке, а в момент получения текущей задачи будем искать самую приоритетную. В таком случае добавление новой задачи будет иметь сложность O(1), а вот получение O(n), потому что каждый раз нужно искать элемент с самым большим приоритетом.<\/p>\n<p>Второй вариант тоже использует обычный массив, только элементы в нем хранятся уже в отсортированном виде. В таком случае сложность меняется зеркально. Добавление нового элемента будет занимать O(n), а получение O(1).<\/p>\n<p>Оба варианта не совсем эффективны, особенно когда очень много данных. Здесь на помощь приходят очереди с приоритетом.<\/p>\n<p>Очереди с приоритетом строятся на базе такой структуры данных как куча. Поэтому важно рассмотреть ее устройство. Куч есть большое множество, но нас сейчас интересует только двоичная куча.<\/p>\n<h2>Двоичная куча<\/h2>\n<p>Двоичная куча представляет из себя двоичное дерево где каждый элемент имеет два дочерних элемента и хранит значение не меньшее чем в дочерних. Они также делятся на два типа: максимальная и минимальная. В первой хранятся элементы в порядке убывания, а во второй в порядке возрастания.<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-max-min-2.png\" width=\"1587\" height=\"712\" alt=\"\" \/>\n<\/div>\n<\/div><p>Для хранения данных кучи удобно использовать обычные массивы. Такой подход занимает мало места и сам по себе достаточно элегантный. На как определить какой элемент массива на какой ссылается? Для этого есть две простые формулы.<\/p>\n<ul>\n<li>2i + 1 — позволяет найти позицию левого дочернего элемента<\/li>\n<li>2i + 2 — позволяет найти позицию правого дочернего элемента<\/li>\n<\/ul>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-array-structure-3.png\" width=\"1500\" height=\"552\" alt=\"\" \/>\n<\/div>\n<\/div><h2>Сортировка дерева<\/h2>\n<div style=\"max-width: 760px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-sorting-4.png\" width=\"2104\" height=\"695\" alt=\"\" \/>\n<\/div>\n<\/div><p>Когда добавляем или удаляем элемент из очереди, свойства кучи могут нарушиться. В таком случае нужно привести ее в порядок. Представим, что есть метод MakeHeap, который принимает на вход индекс элемента и он сортирует его поддерево.<\/p>\n<p>Берем элемент по текущему индексу и сравниваем его с дочерними элементами. Если какой-то дочерний элемент больше текущего, меняем его местами с текущим и спускаемся дальше, чтобы проверить поддерево. Таким образом, мы доходим до конца изначального поддерева сортируя все его поддеревья.<\/p>\n<p>Как это выглядит в коде:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">private void SortHeap(int i)\r\n{\r\n\tvar size = values.Count;\r\n\tvar left = 2 * i + 1;\r\n\tvar right = 2 * i + 2;\r\n\tvar largest = i;\r\n\t\r\n\tif (left &lt; size &amp;&amp; values[left] &gt; values[largest])\r\n\t\tlargest = left;\r\n\t\t\r\n\tif (right &lt; size &amp;&amp; values[right] &gt; values[largest])\r\n\t\tlargest = right;\r\n\t\t\r\n\tif (largest != i)\r\n\t{\r\n\t\tvar temp = values[i];\r\n\t\tvalues[i] = values[largest];\r\n\t\tvalues[largest] = temp;\r\n\t\tSortHeap(largest);\r\n\t}\r\n}<\/code><\/pre><h2>Делаем кучу из обычного массива<\/h2>\n<p>Мы умеем сортировать элементы в поддеревьях, теперь применим этот метод для того, чтобы с обычного массива сделать кучу. Так как потомки гарантированно есть у первых (n\/2 — 1) элементов, то достаточно только для них вызвать метод SortHeap.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">private void MakeHeap()\r\n{\r\n\tfor (int i = values.Count \/ 2 - 1; i &gt;= 0; i--)\r\n\t{\r\n\t\tSortHeap(i);\r\n\t}\r\n}<\/code><\/pre><h2>Добавление нового элемента<\/h2>\n<div style=\"max-width: 760px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-insert-5.png\" width=\"2225\" height=\"650\" alt=\"\" \/>\n<\/div>\n<\/div><p>Новый элемент сначала добавляется в конец массива, после чего запускаем сортировку дерева. Алгоритм получается достаточно простым:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">if (values.Count == 0)\r\n{\r\n\t\/\/ Просто добавляем в элемент в массив, если он пустой\r\n\tvalues.Add(value);\r\n}\r\nelse\r\n{\r\n\t\/\/ Добавляем элемент в конец массива и запускаем сортировку дерева\r\n\tvalues.Add(value);\r\n\tfor (int i = values.Count \/ 2 - 1; i &gt;= 0; i--)\r\n\t{\r\n\t\tSortHeap(i);\r\n\t}\t\t\r\n}<\/code><\/pre><h2>Удаление элемента<\/h2>\n<div style=\"max-width: 760px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-remove-5.png\" width=\"2183\" height=\"652\" alt=\"\" \/>\n<\/div>\n<\/div><p>Чтобы удалить элемент нужно поменять его с последним элементов в куче, потом удалить и запустить сортировку кучи. Так как мы делаем очередь, то будем всегда удалять первый (корневой) элемент кучи.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var size = values.Count;\r\n\t\t\r\nvar last = values.Count - 1;\r\nvar temp = values[0];\r\nvalues[0] = values[last];\r\nvalues[last] = temp;\r\nvalues.RemoveAt(last);\r\n\r\nfor (int i = size \/ 2 - 1; i &gt;= 0; i--)\r\n\tSortHeap(i);\r\n\r\nreturn temp;<\/code><\/pre><h2>Итого<\/h2>\n<p>Куча обладает необходимыми свойствами для создания очереди с приоритетом. Она хранит данные в отсортированном виде, работает быстрее чем обычный массив. Достаточно реализовать такие же методы как в обычной очереди и у нас получиться очередь с приоритетом:<\/p>\n<ul>\n<li>Enqueue — обычная вставка в кучу, ничего даже менять не нужно.<\/li>\n<li>Dequeue — обычное удаление элемента с кучи, только удаляем мы всегда элемент с 0 индексов.<\/li>\n<\/ul>\n<p>Посмотреть полную реализацию можно на <a href=\"https:\/\/dotnetfiddle.net\/Smd2bH\">dotnetfiddle<\/a>.<br \/>\n <\/p>\n",
            "date_published": "2022-07-05T16:29:29-04:00",
            "date_modified": "2022-07-05T16:28:12-04:00",
            "tags": [
                "dotnet",
                "алгоритми та структури даних",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/heap-pic-1.png",
            "_date_published_rfc2822": "Tue, 05 Jul 2022 16:29:29 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "271",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-pic-1.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-max-min-2.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-array-structure-3.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-sorting-4.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-insert-5.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-remove-5.png"
                ]
            }
        },
        {
            "id": "252",
            "url": "https:\/\/www.stefaniuk.website\/all\/collection-in-csharp\/",
            "title": "Коллекции в C#",
            "content_html": "<p>В C# для хранения набора однотипных данных можно использовать массивы. Но с ними не всегда удобно работать потому, что они имеют фиксированный размер и часто бывает сложно угадать, какого размера нужен массив.<\/p>\n<p>Для решения этих задач в C# есть коллекции. Они позволяют динамически изменять свой размер. Также они удобны тем что некоторые из них представляют из себя готовые реализации стандартных структур данных, таких как список, хеш таблица, стек, очередь.<\/p>\n<p>Все коллекции лежат в нескольких пространствах имен:<\/p>\n<ul>\n<li>System.Collections — простые необобщенные коллекции.<\/li>\n<li>System.Collections.Generic — обобщенные коллекции.<\/li>\n<li>System.Collections.Specialized — специальные коллекции.<\/li>\n<li>System.Collections.Concurrent — коллекции для работы в многопоточной среде.<\/li>\n<\/ul>\n<h2>Устройство коллекций<\/h2>\n<p>Все коллекции, так или иначе, реализую интерфейс ICollection, некоторые реализуют интерфейсы IList и IDictionary (которые внутри наследуют ICollection). Этот интерфейс предоставляет минимальный набор методов, которые позволяют реализовать коллекцию.<\/p>\n<p>В свою очередь, ICollection расширяет интерфейс IEnumerable. Он предоставляет нумератор, который позволяет обходить коллекции элемент за элементом. Именно этот интерфейс позволяет использовать коллекции в цикле foreach.<\/p>\n<div style=\"max-width:440px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collections-interfaces.png\" width=\"1496\" height=\"900\" alt=\"\" \/>\n<\/div>\n<\/div><h2>Вместительность коллекций<\/h2>\n<p>Одна из ключевых особенностей коллекций это изменяемый размер. Когда создается экземпляр коллекции она внутри себя инициализирует какую-то структуру данных, зачастую это массив. По умолчанию этот массив имеет определённую вместительность, которую можно просмотреть с помощью свойства Capacity.<\/p>\n<div style=\"max-width:440px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collections-capacity.png\" width=\"900\" height=\"607\" alt=\"\" \/>\n<\/div>\n<\/div><p>После активного наполнения коллекции наступает момент, когда внутренний массив заполнен и мы не можем добавить новый элемент. В таком случае коллекция создаёт новый массив с большей вместительностью (обычно в два раза больше) и копирует туда данные со старого массива.<\/p>\n<div style=\"max-width:440px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collections-capacity-copy.png\" width=\"900\" height=\"554\" alt=\"\" \/>\n<\/div>\n<\/div><p>Поэтому коллекции, которые основаны на массивах имеют сложность вставки:<\/p>\n<ul>\n<li>O(1) — когда вместительности достаточно.<\/li>\n<li>O(n) — когда вместительности недостаточно и нужно копировать данные в массив побольше.<\/li>\n<\/ul>\n<p>Чтобы избежать уменьшения производительности нужно при создании коллекции указать необходимую нам вместительности. Это позволит уменьшить количество копирований.<\/p>\n<h2>Сравнивание и сортировка элементов коллекций<\/h2>\n<p><b>Сравнение<\/b><br \/>\nТакие методы как Contains, IndexOf, LastIndexOf, and Remove используют сравнение элементов для свое работы. Если коллекция является обобщенной, то используются два механизма сравнения:<\/p>\n<ul>\n<li>Если тип реализует интерфейс IEquatable тогда механизм сравнения использует метод Equals этого интерфейса.<\/li>\n<li>Если тип не реализует интерфейс IEquatable тогда для сравнения используется Object.Equals<\/li>\n<\/ul>\n<p>Некоторые коллекции имеют конструктор, который принимает имплементацию IEqualityComparer<T> который используется для сравнения.<\/p>\n<p><b>Сортировка<\/b><br \/>\nСортировка работает похожим образом, как и сравнение, но делиться на два вида: явную сортировку и сортировка по умолчанию.<\/p>\n<p>Сортировка по умолчанию подразумевает, что типы хранящиеся в коллекции реализуют интерфейс IComparable, чьи методы под капотом используют коллекции для сравнения.<\/p>\n<p>Явная сортировка подразумевает, что наши элементы не реализуют интерфейс IComparable, поэтому в качестве параметра метода сортировки нужно передать объект, который реализует интерфейс IComparer.<\/p>\n<p>Если тип не реализует интерфейс IComparable и мы не передали явно тип, который реализует IComparer, то при вызове метода сортировки вылетит исключение.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">System.InvalidOperationException: Failed to compare two elements in the array.\r\n\tSystem.ArgumentException: At least one object must implement IComparable.<\/code><\/pre><h2>Алгоритмическая сложность коллекций<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collection-compexity-4.png\" width=\"1056\" height=\"414\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Источник: <a href=\"http:\/\/c-sharp-snippets.blogspot.com\/2010\/03\/runtime-complexity-of-net-generic.html\">Runtime Complexity of .NET Generic Collection<\/a><\/div>\n<\/div>\n<h2>Список List<T><\/h2>\n<p>Класс List<T> представляет из себя простейший список однотипных элементов, которые можно получить по индексу. Предоставляет методы для поиска, сортировки и изменения списка.<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-list.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var linkedList = new List&lt;string&gt;();\r\nlinkedList.Add(&quot;A&quot;);\r\nlinkedList.Add(&quot;B&quot;);\r\nlinkedList.Add(&quot;C&quot;);<\/code><\/pre><p>Для своей работы списки используют обычные массивы. Это значит что могут быть проблемы с производительностью из-за частого создания нового массива. Также у списков есть еще два нюанса:<\/p>\n<ul>\n<li>Вставка элемента в середину списка приведет к созданию нового массива и копирование данных, что негативно влияет на производительность.<\/li>\n<li>Списки не могут хранить экстремально огромное количество элементов из-за фрагментации адресного пространства.<\/li>\n<\/ul>\n<p>Если эти проблемы существенны для вас, то стоит присмотреться к LinkedList<T> или ImmutableList<T><\/p>\n<h2>Связанный список LinkedList<T><\/h2>\n<p>Класс LinkedList<T> реализует простой двухсвязный список, каждый элемент которого имеет ссылка на предыдущий и следующий элемент.<\/p>\n<p>Каждый элемент списка оборачивается в специальный класс LinkedListNode<T>, который имеет ссылку на следующий элемент (Next), на предыдущий элемент (Previous) и само значение (Value).<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-linked-list.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var linkedList = new LinkedList&lt;string&gt;();\r\nlinkedList.AddFirst(&quot;A&quot;);\r\nlinkedList.AddLast(&quot;B&quot;);\r\nlinkedList.AddLast(&quot;C&quot;);\r\n\t\t\r\nConsole.WriteLine(linkedList.First.Previous == null); \/\/ True\r\nConsole.WriteLine(linkedList.Last.Next == null);   \/\/ True<\/code><\/pre><p>Связанный список позволяет вставлять и удалять элементы со сложностью O (1). Также мы можем удалить элемент и заново вставить в тот же или другой список без дополнительного выделения памяти.<\/p>\n<h2>Словарь Dictionary<TKey, TValue><\/h2>\n<p>Словари хранят данные в виде ключ-значение. Каждый элемент словаря представляет из себя объект структуры KeyValuePair<TKey, TValue>.<\/p>\n<p>В качестве ключа можно использовать любой объект. Ключи должны быть уникальными в рамках коллекции.<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollectios-dict.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var linkedList = new Dictionary&lt;string, string&gt;();\r\nlinkedList.Add(&quot;key1&quot;, &quot;A&quot;);\r\nlinkedList.Add(&quot;key2&quot;, &quot;B&quot;);\r\nlinkedList.Add(&quot;key3&quot;, &quot;C&quot;);<\/code><\/pre><p>Внутри словари построены на базе хеш-таблицы, что позволяет очень быстро вставлять элементы и получать по ключу (сложность O (1)). Сами же хеш-таблицы, в свою очередь, реализованы с помощью массивов.<\/p>\n<p>Для адресации значений внутри коллекции используются хеш коды ключей. Это значит что объект ключа не должен изменяться, потому что это приведет к изменению хеш кода, что в свою очередь приведет к потере данных.<\/p>\n<p><a href=\"https:\/\/github.com\/microsoft\/referencesource\/blob\/5697c29004a34d80acdaf5742d7e699022c64ecd\/mscorlib\/system\/collections\/generic\/dictionary.cs\">Исходники Dictionary<\/a><\/p>\n<h2>Стек Stack<T> и Очередь Queue<T><\/h2>\n<p>Очереди и стеки полезны, когда нужно временно хранить какие-то элементы, то есть удалять элемент после его извлечения. Также они позволяют определить строгую очередность записи и извлечения элементов.<\/p>\n<p>Стеки Stack<T> — реализуют подход LIFO (last in — first out).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var stack = new Stack&lt;int&gt;();\r\nstack.Push(1); \/\/ stack = [1]\r\nstack.Push(2); \/\/ stack = [1,2]\r\nvar item = stack.Pop(); \/\/ stack = [1], item = 2<\/code><\/pre><p>Очереди Queue<T> — реализуют подход (first in — first out).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var queue = new Queue&lt;int&gt;();\r\nqueue.Enqueue(1); \/\/ queue = [1]\r\nqueue.Enqueue(2); \/\/ queue = [1,2]\r\nitem = queue.Dequeue(); \/\/ queue = [2], item = 1<\/code><\/pre><p>Внутри они реализованы с помощью обычных массивов.<\/p>\n<h2>Множества HashSet<T> и SortedSet<T><\/h2>\n<p>Эти классы реализуют математические множества. По своей природе множество — набор уникальных элементов с общими характеристиками, в нашем случае одного типа.<\/p>\n<p>Также множества отличаются от обычных списков тем что они предоставляют набор методов, которые реализуют операции с <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Теория_множеств\">теории множеств<\/a>.<\/p>\n<p>Внутренняя реализация этих классов отличается:<\/p>\n<ul>\n<li>HashSet<T> — множество, построенное на базе хеш-таблицы.<\/li>\n<li>SortedSet<T> — отсортированное множество, построенное на базе красно-черного дерева.<\/li>\n<\/ul>\n<p><br><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">ISet&lt;int&gt; set = new HashSet&lt;int&gt; { 1, 2, 3, 4, 5 };\r\n \r\nset.UnionWith(new[] { 5, 6 });              \/\/ set = { 1, 2, 3, 4, 5, 6 }\r\nset.IntersectWith(new[] { 3, 4, 5, 6, 7 }); \/\/ set = { 3, 4, 5, 6 }\r\nset.ExceptWith(new[] { 6, 7 });             \/\/ set = { 3, 4, 5 }\r\nset.SymmetricExceptWith(new[] { 4, 5, 6 }); \/\/ set = { 3, 6 }<\/code><\/pre><p>Можно заметить что LINQ предоставляет несколько похожих операций (Distinct, Union, Intersect, Except), которые можно выполнить с любой коллекцией. Но HastSet предоставляет намного больший набор операций с множествами.<\/p>\n<p>Основная разница в том что методы множеств изменяют текущую коллекцию, в то время как LINQ методы всегда создают новый экземпляр коллекции.<\/p>\n<h2>KeyedCollection<TKey,TItem><\/h2>\n<p>KeyedCollection это абстрактный класс, который позволяет построить собственную коллекцию.<\/p>\n<p>Эта коллекция является гибридом между списками (IList<T>) и словарями (IDictionary<T>). От списков ей досталась возможность получать элементы по индексу, а от словарей возможность получать элемент по ключу.<\/p>\n<p>В отличие от словарей, элемент KeyedCollection коллекции не является парой ключ-значение, вместо этого весь элемент является значением, а в качестве ключа используется его поле, свойство или любое другое значение. Для получения ключа используется абстрактный метод, который является обязательным для реализации. GetKeyForItem<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-keyed.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var keyedCollection = new UserCollection();\r\nkeyedCollection.Add(new User {\r\n\tId = 1,\r\n\tName = &quot;A&quot;\r\n});\r\nkeyedCollection.Add(new User {\r\n\tId = 2,\r\n\tName = &quot;B&quot;\r\n});\r\n\t\t\r\nConsole.WriteLine(keyedCollection[2].Name); \/\/ B\r\n\t\r\npublic class UserCollection: KeyedCollection&lt;int, User&gt;\r\n{\r\n\tprotected override int GetKeyForItem(User user) =&gt; user.Id;\r\n}\r\n\t\r\npublic class User\r\n{\r\n\tpublic int Id {get;set;}\r\n\tpublic string Name {get;set;}\r\n}<\/code><\/pre><p>Внутри KeyedCollection построен на базе двух структур данных. Для быстрого получения данных по ключу используется словарь, а для получение элемента по индексу используется массив.<\/p>\n<h2>NameValueCollection<\/h2>\n<p>Представляет из себя коллекцию, которая похожа на словарь но в качестве ключей и значений используются строки. Элементы можно получить как по индексу так и по ключу.<\/p>\n<p>Особенной эту коллекцию делает то что однин ключ может содержать несколько эллементов.<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-named.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var namedCollection = new NameValueCollection();\r\nnamedCollection.Add(&quot;key1&quot;, &quot;value1&quot;);\r\nnamedCollection.Add(&quot;key2&quot;, &quot;value2&quot;);\r\nnamedCollection.Add(&quot;key1&quot;, &quot;value3&quot;);\r\n\r\nConsole.WriteLine(namedCollection.Count);   \/\/ 2\r\nConsole.WriteLine(namedCollection[&quot;key1&quot;]); \/\/ value1,value3<\/code><\/pre><h2>Иммутабельные коллекции<\/h2>\n<p>Иммутабельные коллекции не входят в стандартную библиотеку классов (BCL). Для их использования нужно установить System.Collections.Immutable NuGet пакет.<\/p>\n<p>Они позволяют безопасно работать в многопоточной среде. Вместо того чтобы использовать блокировки синхронизации, как это делают многопоточные коллекции, неизменяемые коллекции не могут быть изменены после создания. Это автоматически делает их безопасными для использования в многопоточных сценариях, так как нет возможности другому потоку изменить коллекцию.<\/p>\n<p>Сами же коллекции можно поделить на несколько видов:<\/p>\n<ul>\n<li>Mutable — обычные коллекции которые поддерживают изменения.<\/li>\n<li>Immutable — коллекции, которые полностью запрещают изменения. Хотя на самом деле любое изменение иммутабельной коллекции приводит к созданию новой.<\/li>\n<li>ReadOnly — обертки над стандартными коллекциями, которые не дают поменять данные. Из-за того что это всего лишь обертка мы можем поменять данные в оригинальной коллекции и ead only коллекция подтянет изменения.<\/li>\n<\/ul>\n<p>Детальнее можно ознакомиться в статье: <a href=\"https:\/\/devblogs.microsoft.com\/premier-developer\/read-only-frozen-and-immutable-collections\/\">Read only, frozen, and immutable collections<\/a>.<\/p>\n<h2><b>Иммутабельные стеки ImmutableStack<T> и очереди ImmutableQueue<T><\/b><\/h2>\n<p>Ничем не отличаются от обычных стеков и очередей, кроме того, что являются не изменяемыми.<\/p>\n<p>Для реализации иммутабельных стеков\/очередей массивы не подойдут. Причина заключается в том, что на каждое изменение придётся делать полную копию массива что очень неэффективно.<\/p>\n<p>Поэтому для работы иммутабельных стеков\/очередей используется связанный список. При изменении коллекции нужно всего лишь создать новый элемент, который ссылается на предыдущее значение. В итоге не происходит никакого копирования и экономится память.<\/p>\n<h2><b>Иммутабельные списки ImmutableList<\/b><\/h2>\n<p>Под капотом используют сбалансированное бинарное дерево вместо массива или связанного списка.<\/p>\n<p>Массивы не подходят из-за накладных расходов на их использование. Связанный список тоже не подойдет, потому что ImmutableList поддерживает обращение по индексу из-за чего нужно долго перебирать связанные элементы. Поэтому для нормальной работы иммутабельных списков используют сбалансированное бинарное дерево.<\/p>\n<h2><b>Иммутабельные массивы ImmutableArray<\/b><\/h2>\n<p>По сути, это небольшая прослойка над обычными массивами и все. Любые мутабельные операции приводят к копированию массива. Из-за этого скорость добавления элементов равна O (n), но в то же время получение элемента по индексу занимает O (1).<\/p>\n<p>Итерация по массиву работает в несколько раз быстрее чем у других неизменяемых коллекциях.<\/p>\n<h2><b>Иммутабельные словари ImmutableDictionary<\/b><\/h2>\n<p>Неизменяемые словари внутри работают на базе сбалансированного дерева, но с одной особенностью. Каждый элемент внутри коллекции представлен в виде отдельного дерева (ImmutableList<KeyValuePair<TKey, TValue>>). Так что по своей сути иммутабельные словари — это деревья деревьев.<\/p>\n<p>Из-за своей особенности иммутабельные словари потребляют очень много памяти и долго работают. Поэтому стоит аккуратно их использовать.<\/p>\n<h2><b>Особенности использования<\/b><\/h2>\n<p>Как было выше сказано все операции изменения коллекций приводят к созданию новой коллекции. Нужно помнить, что необходимо использовать новый экземпляр вместо старого.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var immutableList = new[] { 1, 2, 3 }.ToImmutableList();\r\nimmutableList = immutableList.Add(4);<\/code><\/pre><p>По идее чтобы было проще, мы можем объединить все изменения в цепочку:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">immutableList = immutableList\r\n    .Add(5)\r\n    .Add(6)\r\n    .Add(7);<\/code><\/pre><p>Однако такого способа стоит избегать, потому что каждый вызов метода создает новый экземпляр коллекции. Хотя неизменяемые коллекции реализованы таким образом, чтобы повторно использовать как можно большую часть оригинальной коллекции при создании нового экземпляра, некоторые выделения памяти все же необходимы. Это означает больше работы для сборщика мусора.<\/p>\n<p>Чтобы минимизировать проблему стоит использовать методы, которые могут выполнить нужные изменения за один вызов. Например:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">immutableList = immutableList.AddRange(new[] { 5, 6, 7 });<\/code><\/pre><p>Но такие методы позволяют сделать только один вид изменения. Например у нас нет единого метода, который позволяет добавить и удалить одну запись:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">immutableList = immutableList\r\n    .Add(6)\r\n    .Remove(2);<\/code><\/pre><p>Для решения этой проблемы иммутабельные коллекции предоставляют билдеры (builders).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var builder = immutableList.ToBuilder();\r\nbuilder.Add(6);\r\nbuilder.Remove(2);\r\nimmutableList = builder.ToImmutable();<\/code><\/pre><p>Внутри себя билдеры используют соответствующую мутабельную коллекцию, что позволяет выполнить все операции над одним экземпляром коллекции. Только после вызова метода ToImmutable экземпляр снова будет неизменяемым. Таким образом можно сократить объем работы уборщика мусора.<\/p>\n<h2>Источники и доп. материалы<\/h2>\n<ul>\n<li><a href=\"https:\/\/www.dotnetcurry.com\/csharp\/1466\/csharp-dotnet-collection-class\">How to Choose the Right .NET Collection Class?<\/a> Отличная статья про то какую коллекцию выбрать в .NET. И вообще хорошо хоть и поверхностно описаны конкурентные и неизменяемые коллекции.<\/li>\n<li><a href=\"http:\/\/staticvoidmain.cognitioab.se\/2013\/11\/28\/net-collections-comparing-performance-and-memory-usage\/\">.NET Collections: comparing performance and memory usage<\/a>. Сравнивались коллекции-словари и среди них лучше всего отработал Dictionary, SortedList в свою очередь в среднем потреблял в два раза меньше памяти чем обычный словарь. Хуже всего себя показал отсортированный словарь SortedDictionary.<\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/collections\/\">Collections and Data Structures<\/a>. Отличное описание коллекций на MSDN.<\/li>\n<\/ul>\n",
            "date_published": "2021-04-21T11:10:51-04:00",
            "date_modified": "2021-04-26T07:16:48-04:00",
            "tags": [
                "dotnet",
                "алгоритми та структури даних",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/collections-linked-list.png",
            "_date_published_rfc2822": "Wed, 21 Apr 2021 11:10:51 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "252",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-linked-list.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collectios-dict.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-keyed.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-named.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-list.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-interfaces.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-capacity.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-capacity-copy.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collection-compexity-4.png"
                ]
            }
        },
        {
            "id": "224",
            "url": "https:\/\/www.stefaniuk.website\/all\/zhiznenny-cikl-zaprosov-v-asp-net-core-mvc\/",
            "title": "Жизненный цикл запросов в ASP.NET Core",
            "content_html": "<p>Жизненный цикл — это цепочка событий, этапов и компонентов, которые взаимодействуют друг с другом, чтобы обработать запрос пользователя и предоставить ему ответ.<\/p>\n<p>Общий вид на жизненный цикл ASP.NET Core MVC приложений:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/main-cycle.png\" width=\"850\" height=\"216\" alt=\"\" \/>\n<\/div>\n<p>Он содержит в себе несколько этапов:<\/p>\n<ul>\n<li>Middlewares<\/li>\n<li>Routing<\/li>\n<li>Controller Initialization<\/li>\n<li>Controller action execution<\/li>\n<li>Result Execution<\/li>\n<li>View Rendering<\/li>\n<\/ul>\n<h2>Middlewares<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/middlewares.png\" width=\"580\" height=\"249\" alt=\"\" \/>\n<\/div>\n<p>Middlewares представляют из себя базовые блоки, с помощью которых строится HTTP пайплайн. Такой пайплайн представляет из себя конвейер, который обрабатывает каждый запрос. Каждый блок получает запрос и смотрит на него, если может предоставить ответ — возвращает его, если нет, передаёт запрос следующему блоку.<\/p>\n<p>В ASP.NET Core у нас есть 4 метода, с помощью которых мы можем создавать свои middlewares.<\/p>\n<ul>\n<li>Use<\/li>\n<li>Run<\/li>\n<li>Map<\/li>\n<li>MapWhen<\/li>\n<\/ul>\n<p><a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/middleware\/?view=aspnetcore-3.1\">Официальная документация<\/a><\/p>\n<h2>Routing<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/Routing.png\" width=\"800\" height=\"362\" alt=\"\" \/>\n<\/div>\n<p>Маршрутизация позволяет найти для каждого URL подходящий обработчик, а также извлекает все параметры из URL и красиво их нам предоставляет. Для работы роутинга нам нужны две middleware:<\/p>\n<ul>\n<li>UseRouting<\/li>\n<li>UseEndpoints<\/li>\n<\/ul>\n<p><b>Как все это работает?<\/b><\/p>\n<p>UseRouting встраивает класс EndpointRoutingMiddleware, он смотрит на конечные точки, которые есть в приложении и выбирает подходящую. Выбор происходит на основании URL и заголовков. После того как нужный обработчик найден (Request delegate) его присваивают свойству Endpoint(IEndpointFeature) внутри текущего HttpContext. В дальнейшем мы можем получить его значения с помощью метода GetEndpoint.<\/p>\n<p>UseEndpoints встраивает EndpointMiddleware. Он отвечает за выполнение установленной ранее конечной точки.<\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/routing?view=aspnetcore-3.1\">Официальная документация<\/a><\/p>\n<h2>Controller initialization<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/ControllerInitialization.png\" width=\"611\" height=\"320\" alt=\"\" \/>\n<\/div>\n<p>Для обеспечения работы контроллеров у есть два класса: ResourceInvoker, ControllerActionInvoker.<\/p>\n<p><b>ResourceInvoker<\/b> является абстрактным классом, который содержит общую логику работы с ресурсом, обработку исключений и результата.<\/p>\n<p><b>ControllerActionInvoker<\/b> наследует ResourceInvoker и добавляет в него логику работы с контроллерами и их методами.<\/p>\n<p>Каждый из этих классов содержит машину состояний, где каждое состояние отвечает за свой этап жизненного цикла контроллера.<\/p>\n<p>Исходники классов можно посмотреть на github (<a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/blob\/169b7db307935c3646b0d12906ba2c612b912b77\/src\/Mvc\/Mvc.Core\/src\/Infrastructure\/ResourceInvoker.cs\">ResourceInvoker<\/a>, <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/blob\/b23ea5b6683b08e8d168ccf49e0fc8515077ee2e\/src\/Mvc\/Mvc.Core\/src\/Infrastructure\/ControllerActionInvoker.cs\">ControllerActionInvoker<\/a>).<\/p>\n<h2>Action workflow<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/action-life-cycle.png\" width=\"800\" height=\"459\" alt=\"\" \/>\n<\/div>\n<p>Работа каждого метода контроллера состоит из своего жизненного цикла. Основой которого являются фильтры. Каждый фильтр может отрабатывать как и перед запуском действия, так и после.<\/p>\n<p>Общий вид этого жизненного цикла:<\/p>\n<ul>\n<li>Фильтры авторизации<\/li>\n<li>Фильтры ресурсов<\/li>\n<li>Привязки моделей<\/li>\n<li>Фильтры действий<\/li>\n<li>Выполнение метода контроллера<\/li>\n<li>Фильтры исключений<\/li>\n<li>Фильтры результатов<\/li>\n<li>Выполнение результата<\/li>\n<li>Фильтры результатов<\/li>\n<\/ul>\n<p>Детальнее про фильтры можно почитать <a href=\"https:\/\/metanit.com\/sharp\/aspnet5\/18.1.php\">на метаните<\/a>.<\/p>\n<h2>Result execution<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/action-result.png\" width=\"850\" height=\"391\" alt=\"\" \/>\n<\/div>\n<p>Методы контроллера возвращают объекты результата, которые в дальнейшем преобразовываются в соответствующее представление. Объект результата должен наследовать абстрактный класс ActionResult. Фреймворк предоставляет нам большое множество готовых классов.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/action-results.png\" width=\"650\" height=\"287\" alt=\"\" \/>\n<\/div>\n<p>Общий вид того как происходит рендеринг представлений:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/Render-cycle.png\" width=\"800\" height=\"344\" alt=\"\" \/>\n<\/div>\n<h2>Источники<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/?view=aspnetcore-3.1\">Официальная документация<\/a><\/li>\n<li><a href=\"https:\/\/app.pluralsight.com\/library\/courses\/aspnet-core-3-mvc-request-life-cycle\/table-of-contents\">Курс на pluralsight<\/a><\/li>\n<\/ul>\n",
            "date_published": "2020-06-12T16:38:19-04:00",
            "date_modified": "2023-03-12T18:36:23-04:00",
            "tags": [
                "asp.net core",
                "dotnet"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/main-cycle.png",
            "_date_published_rfc2822": "Fri, 12 Jun 2020 16:38:19 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "224",
            "_e2_data": {
                "is_favourite": true,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/main-cycle.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/middlewares.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/Routing.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/ControllerInitialization.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/action-life-cycle.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/action-result.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/action-results.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/Render-cycle.png"
                ]
            }
        },
        {
            "id": "207",
            "url": "https:\/\/www.stefaniuk.website\/all\/async-programming-in-csharp\/",
            "title": "Асинхронное программирование в C#",
            "content_html": "<p>Небольшой конспект доклада об асинхронности в .NET. Cоветую его посмотреть, будет полезен как новичкам, так и уже опытным ребятам.<\/p>\n<div class=\"e2-text-video\">\n<iframe src=\"https:\/\/www.youtube.com\/embed\/OoSFGENdNPo?enablejsapi=1\" allow=\"autoplay\" frameborder=\"0\" allowfullscreen><\/iframe>\n<\/div>\n<h2>Что такое асинхронность?<\/h2>\n<p>Асинхронность — выполнение программного кода, не блокирующее потоки во время ожидания.<\/p>\n<p>Например: делаем запрос в бд для получения некоторых данных.<br \/>\nУ нас есть поток и запрос, который выполняет этот поток. При синхронном выполнении наш поток будет ждать, пока БД не ответит. В асинхронном случае наш поток будет выполнять другой запрос, пока БД обрабатывает текущий.<\/p>\n<h2>TAP<\/h2>\n<p>С приходом .NET Framework 4.0 появился новый способ для работы с асинхронностью — TAP. По сути, это набор классов, которые предоставляют более удобный интерфейс для работы с асинхронным кодом. Но мы всё ещё можем использовать старые подходы: APM и EAP. Но Майкрософт советует использовать TAP.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/apm-vs-eap-vs-tap.png\" width=\"550\" height=\"295\" alt=\"\" \/>\n<\/div>\n<p>Центральным классов в TAP является Task. Он описывает отдельную задачу, выполнение которой может завершиться в какой-то момент. <\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/asynchronous-programming-patterns\/\">Детальнее про подходы работы с многопоточностью.<\/a><\/p>\n<h2>Как создать задачу?<\/h2>\n<p>Фреймворк предоставляет множество способов для создания и запуска задач, а также оборачивания старого кода (APM и EAP).<\/p>\n<p>Фабрики запущенных задач<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Task.Run(Action\/Func)\r\nTask.Factory.StartNew(Action\/Func)<\/code><\/pre><p>Фабрики завершенных задач<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Task.FromResult(Result)\r\nTask.FromCanceled(Result)\r\nTask.FromException(Result)\r\nTask.CompletedTask<\/code><\/pre><p>Конструктор<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var task = new Task(Action\/Func)\r\ntask.Start()<\/code><\/pre><p>Фабрики для оборачивания в таски<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Task.Factory.FromAsync(APM pattern)\r\nTaskCompletionSource(EAP, APM, etc)<\/code><\/pre><h2>Класс TaskScheduler<\/h2>\n<p>Класс, который содержит стратегию запуска и планирования задач Task. Существуют две стандартные реализации: ThreadPoolTaskScheduler и SynchronizationContextTaskScheduler. Первая запускает задачи с помощью пула потоков, а вторая — использует текущий контекст синхронизации.<\/p>\n<p>Чтобы указать планировщику, что наша задача продолжительная мы можем использовать флаг TaskCreationOptions.LongRunning (указывается при создании задачи). Это гарантирует, что под нашу задачу выделиться отдельный поток и в него не будут планироваться продолжения других операций.<\/p>\n<p>У каждого потока есть свой «текущий» контекст, который необязательно должен быть уникальным. Иногда нам нужно, чтобы завершение или продолжение асинхронной операции выполнялись в том же контексте, откуда было запущено. Например, приложения с UI. После нажатия на кнопку мы запускаем асинхронную операцию, результат которой нужно вывести на экран. Но мы не сможем получить доступ к UI элементу, так как он находится в другом потоке. Для этого нам нужно запустить обновление данных в контексте UI потока. Именно для этого и нужен контекст синхронизации.<\/p>\n<p><a href=\"https:\/\/www.lshnk.me\/2018\/03\/12\/%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%BA%D1%81%D1%82-%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8-%D0%BF%D1%80%D0%B8-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B5-%D1%81-task-%D0%B8-thread\/\">Контекст синхронизации при работе с Task и Thread<\/a><\/p>\n<h2>Комбинирование задач<\/h2>\n<p>Есть ситуации, когда нам нужно ожидать выполнения нескольких задач, для этого мы можем использовать комбинаторы задач.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Task.WaitAll();\r\nTask.WaitAny();\r\nTask.WhenAll();\r\nTask.WhenAny();<\/code><\/pre><p>Но нужно быть внимательным, так как WaitAll и WaitAny являются блокирующими операциями. Вместо них желательно использовать WhenAll и WhenAny.<\/p>\n<h2>Как получить результат?<\/h2>\n<p>Получить результат из операции можно несколькими способами. Некоторые из них являются блокирующими. Старайтесь избегать синхронного ожидания, так как это может привести к дедлокам.<\/p>\n<p>Блокирующее получение результата:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">.Result\r\n.GetAwaiter().GetResult() - \/\/лучше не использовать так как этот метод предназначен для компилятора, а не для нас.\r\n.Wait()<\/code><\/pre><p>Асинхронное:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">await<\/code><\/pre><p><a href=\"https:\/\/www.lshnk.me\/2018\/03\/17\/%D1%87%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-sync-over-async-deadlock\/\">Что такое sync over async deadlock?<\/a><\/p>\n<h2>Продолжение задач<\/h2>\n<p>Есть ситуации, когда нам нужно выполнить несколько операций одну за одной. Для этого у нас есть метод ContinueWith, с помощью которого можно построить цепочку продолжений.<\/p>\n<p>Пример:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var task = Task.Run(() =&gt; Console.WriteLine(&quot;Async task&quot;));\r\nvar nextTask = task.ContinueWith((prevTask) =&gt; {\r\n\tConsole.WriteLine(&quot;Continuation&quot;);\r\n});\r\nnextTask.ContinueWith((prevTask) =&gt; {\r\n\tConsole.WriteLine(&quot;Last Continuation&quot;);\r\n});<\/code><\/pre><p>Мы можем настраивать условия продолжения используя класс TaskContinuationOptions. С его помощью мы можем строить дерево задач, запускать продолжения по условиям и т. д.<\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.threading.tasks.taskcontinuationoptions?view=netframework-4.8\">Официальная документация<\/a><\/p>\n<h2>Обработка исключений<\/h2>\n<p>Работа с исключениями в асинхронном коде отличается от синхронного. Мы можем и не узнать об исключении, если не будем явно его обрабатывать. А в более старых версиях .NET это могло привести к падению приложения.<\/p>\n<p>Обработка исключения при использовании await:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">try\r\n{\r\n\tawait Task.Run(() =&gt; { throw new Exception(); });\r\n}\r\ncatch (Exception e)\r\n{\r\n\t\/\/ \r\n}<\/code><\/pre><p>По умолчанию ContinueWith выполняется независимо от того произошло ли исключение в предыдущей задаче. Но мы можем его настроить двумя способами: в самом продолжении проверить предыдущую задачу или сделать отдельное продолжение, которое будет вызвано только при возникновении исключения.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Task.Run(() =&gt; { throw new Exception(); })\r\n\t.ContinueWith((prev) =&gt; {\r\n\t\tif (prev.IsFaulted)\r\n\t\t{\r\n\t\t\t\/\/\r\n\t\t}\r\n\t});\r\n\r\nTask.Run(() =&gt; { throw new Exception(); })\r\n\t.ContinueWith((prev) =&gt; {\r\n\t\t\r\n\t}, TaskCreationOptions.OnlyOnfaulted);<\/code><\/pre><p>Стоит избегать асинхронных методов, которые ничего не возвращают (async void). Так как при возникновении исключения внутри метода, мы просто не сможем его обработать.<\/p>\n<p>Комбинаторы задач также по-разному предоставляют исключения:<\/p>\n<ul>\n<li>Task.WaitAll() — вернёт AggregateException;<\/li>\n<li>Task.WhenAll() — вернёт только первое исключение;<\/li>\n<\/ul>\n<h2>Отмена задач<\/h2>\n<p>Если нам нужно отменить выполнение асинхронной задачи мы можем воспользоваться классом CancellationToken. Про него <a href=\"https:\/\/stefaniuk.website\/all\/cancellationtoken\/\">я рассказывал раньше<\/a>.<\/p>\n<h2>async\/await<\/h2>\n<p>Основная идея этого подхода заключается в написании асинхронного кода как синхронного. Это позволило создать более дружелюбный интерфейс для работы с асинхронностью.<\/p>\n<p>Что делает async?<\/p>\n<ul>\n<li>Создаёт машину состояний, которая обрабатывает все продолжения и синхронизации.<\/li>\n<li>Разрешает использовать await.<\/li>\n<li>Позволяет передавать вверх по стеку результат и исключения, используя Task.<\/li>\n<\/ul>\n<p>Что делает await?<\/p>\n<ul>\n<li>Позволяет не блокирующее ожидать результат.<\/li>\n<li>Запуск продолжения в нужном потоке.<\/li>\n<li>Возвращает результат или исключение.<\/li>\n<\/ul>\n<p>Заблуждения по поводу await<\/p>\n<ul>\n<li>Запускает операцию асинхронно.<\/li>\n<li>Являться синтаксическим сахаром над Task.ContinueWtih.<\/li>\n<li>Обязательно запускает продолжение в новый поток.<\/li>\n<li>Всегда работает асинхронно.<\/li>\n<\/ul>\n<h2>Советы<\/h2>\n<ul>\n<li>Не используйте комбинацию <i>async void<\/i> — вы не сможете отловить исключение, а также ожидать выполнение метода.<\/li>\n<li>Методы помеченные как <i>async<\/i> должны внутри себя содержать <i>await<\/i>.<\/li>\n<li>Старайтесь вежде использовать не блокирующее ожидание <i>await<\/i> вместо блокирующего.<\/li>\n<li>Для выполнения продолжительных операций используйте Task.Factory.StartNew вместе с TaskCreationOptions.LognRunning.<\/li>\n<li>Используйте await t.ConfigureAwait(false) для библиотечного кода.<\/li>\n<li>Не используйте совместно await и ContinueWith, так как они ничего не знают друг о друге.<\/li>\n<\/ul>\n<h2>Что осталось за кадром?<\/h2>\n<ul>\n<li>Асинхронные потоки<\/li>\n<li>ValueTask<\/li>\n<li>Progress<\/li>\n<\/ul>\n<h2>Ссылки<\/h2>\n<ul>\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=lh8cT6qI-nA\">Андрей Часовских — Async\/await: собираем грабли<\/a><\/li>\n<li><a href=\"https:\/\/ru.stackoverflow.com\/questions\/491632\/%D0%9D%D1%83%D0%B6%D0%B5%D0%BD-async-await-%D0%B8%D0%BB%D0%B8-%D0%BD%D0%B5-%D0%BD%D1%83%D0%B6%D0%B5%D0%BD\">Нужен async\/await или не нужен?<\/a><\/li>\n<\/ul>\n",
            "date_published": "2020-03-06T13:02:56-04:00",
            "date_modified": "2020-04-07T06:21:08-04:00",
            "tags": [
                "dotnet"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/remote\/youtube-OoSFGENdNPo-cover.jpg",
            "_date_published_rfc2822": "Fri, 06 Mar 2020 13:02:56 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "207",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/jquery\/jquery.js",
                    "system\/library\/media-seek\/media-seek.js"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/remote\/youtube-OoSFGENdNPo-cover.jpg",
                    "https:\/\/www.stefaniuk.website\/pictures\/apm-vs-eap-vs-tap.png"
                ]
            }
        },
        {
            "id": "171",
            "url": "https:\/\/www.stefaniuk.website\/all\/create-workplace-messenger-bot\/",
            "title": "Как я делал бота для Facebook workplace",
            "content_html": "<p>Дали мне задачу: написать бота для мессенджера workplace, с помощью которого можно получать уведомления из нашей CRM и управлять разными вещами. Расскажу о разных интересных вещах с которыми я столкнулся. Бота писал с помощью ASP.NET Core Web API.<\/p>\n<h2>Вебхуки<\/h2>\n<p>Для того чтобы бот мог обрабатывать запросы и сообщения от Facebook нам надо настроить вебхуки. Webhook — механизм оповещения системы о событиях. Для того чтобы Facebook принял наш хук, он должен обрабатывать как GET так и POST запросы.<\/p>\n<p class=\"note\"><a href=\"https:\/\/developers.facebook.com\/docs\/messenger-platform\/getting-started\/webhook-setup\">Подробнее о Webhook в официальной документации<\/a><\/p>\n<p>GET запрос служит для валидации работы нашего эндпоинта. POST принимает данные связанные с активностью пользователя, будь то нажатие на кнопки или другая активность.<\/p>\n<p class=\"cut-button\">Показать код метода-обработчика GET запроса<\/p>\n<div class=\"cut-content\"><pre class=\"e2-text-code\"><code class=\"\">public IActionResult Receive(\u2028[FromQuery(Name = &quot;hub.mode&quot;)] string mode,\u2028\r\n    [FromQuery(Name = &quot;hub.challenge&quot;)] string challenge,\u2028\r\n    [FromQuery(Name = &quot;hub.verify_token&quot;)] string verifyToken)\r\n{\u2028\r\n   if (string.IsNullOrEmpty(verifyToken)) {\u2028\r\n       return Unauthorized();\u2028\r\n    }\u2028\u2028\r\n    if (verifyToken.Equals(FacebookEnvironment.FacebookVToken)) {\u2028\r\n        return Ok(challenge);\u2028\r\n    }\u2028\r\n    return Unauthorized();\u2028\r\n}<\/code><\/pre><p>В качестве verify_token используется токен, который мы указали при регистрации нашего хука.<\/p>\n<\/div><p class=\"cut-button\">Показать код метода-обработчика POST запроса<\/p>\n<div class=\"cut-content\"><pre class=\"e2-text-code\"><code class=\"\">public async Task&lt;IActionResult&gt; Receive([FromBody]FbResponse response = null)\r\n{\r\n    if (response is null) {\r\n        return BadRequest();\r\n    }\r\n\r\n    if (response.Object != &quot;page&quot;) {\r\n        return Ok();\r\n    }\r\n\r\n    foreach (var entry in response.Entries) {\r\n        foreach (var message in entry.Messaging) {\r\n            await PrepareMessageAsync(message);\r\n        }\r\n    }\r\n    return Ok(&quot;EVENT_RECEIVED&quot;);\r\n}<\/code><\/pre><\/div><h2>Авторизация запросов Facebook<\/h2>\n<p>Для авторизации Facebook использует специальный http заголовок (X-Hub-Signature), в нем он передает некую сигнатуру с помощью которой мы можем авторизовать запрос. Для того чтобы добавить такую функциональность в наш контроллер, добавим фильтр.<\/p>\n<p class=\"cut-button\">Пример кода, для проверки подписи<\/p>\n<div class=\"cut-content\"><pre class=\"e2-text-code\"><code class=\"\">private const string Sha1Prefix = &quot;sha1=&quot;;\r\n\r\npublic static bool Validate(string signature, string contentString) {\r\n    if (!signature.StartsWith(Sha1Prefix, StringComparison.OrdinalIgnoreCase)) {\r\n        return false;\r\n    }\r\n    var secret = Encoding.ASCII.GetBytes(FacebookEnvironment.AppSecret);\r\n    var signatureWithoutPrefix = signature.Substring(Sha1Prefix.Length);\r\n    var content = Encoding.ASCII.GetBytes(contentString);\r\n    return GetIsHashValid(secret, signatureWithoutPrefix, content);\r\n}\r\n\r\nprivate static bool GetIsHashValid(byte[] secret, string signature, byte[] content) {\r\n    using var hmac = new HMACSHA1(secret);\r\n    var hash = hmac.ComputeHash(content);\r\n    var hashString = ToHexString(hash);\r\n    return hashString.Equals(signature);\r\n}\r\n\r\nprivate static string ToHexString(IReadOnlyCollection&lt;byte&gt; bytes)\r\n{\r\n    var builder = new StringBuilder(bytes.Count * 2);\r\n    foreach (var b in bytes)\r\n    {\r\n        builder.AppendFormat(&quot;{0:x2}&quot;, b);\r\n    }\r\n\r\n    return builder.ToString();\r\n}<\/code><\/pre><\/div><h2>Тестирование бота<\/h2>\n<p class=\"note-md\"><a href=\"http:\/\/winitpro.ru\/index.php\/2017\/11\/03\/ustanovka-besplatnogo-ssl-sertifikata-lets-encrypt-na-iis-v-windows-server-2012-r2\/\">Как установить letsencrypt сертификат для IIS<\/a>. Если же вы используете связку в виде ubuntu и nginx вам подойдет <a href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/nginx-let-s-encrypt-ubuntu-18-04-ru\">эта инструкция<\/a>.<\/p>\n<p>Для тестирования нужно развернуть бот на сервере, который смотрит в мир. Также необходимо чтобы у сервера было доменное имя и валидный SSL сертификат. В моем случае, в качестве сервера выступала машина на винде, так как другой внутри нашей сети не было. Как мне показалось захостить приложение написанное на .NET Core намного проще под Ubuntu + nginx нежели под Windows + IIS. В качестве поставщика сертификатов выбрал letsencrypt, так как они предоставляют бесплатный сертификат на 3 месяца, с возможностью дальнейшего обновления.<\/p>\n",
            "date_published": "2019-11-26T10:22:26-04:00",
            "date_modified": "2023-06-02T20:26:31-04:00",
            "tags": [
                "asp.net core",
                "dotnet",
                "програмування",
                "проекти",
                "робота"
            ],
            "_date_published_rfc2822": "Tue, 26 Nov 2019 10:22:26 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "171",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "161",
            "url": "https:\/\/www.stefaniuk.website\/all\/parallel-asynchronous-multithreading-programming\/",
            "title": "Parallel, Asynchronous, Multithreading programming",
            "content_html": "<h2>Многопоточное программирование<\/h2>\n<p>Код может выполнятся в разных потоках. Например основной UI поток и набор потоков для обработки данных. В данном случае нет гарантии что потоки будут работать параллельно. Обычно это зависит от процессора. Потоки «абстрагируют» от пользователя низкоуровневые детали и позволяют выполнять более чем одну работу «параллельно».<\/p>\n<h2>Параллельное программирование<\/h2>\n<p>Подразумевает что некоторая задача разбивается на несколько независимых подзадач, которые можно выполнить параллельно и потом объединить результаты.<br \/>\nПримером такой задачи может быть Parallel LINQ<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">IEnumerable&lt;Data&gt; yourData = GetYourData();\r\nvar result = yourData.AsParallel() \/\/ начинаем обрабатывать параллельно\r\n  .Select(d =&gt; d.CalcAmount()) \/\/ Вычисляем параллельно\r\n  .Where(amount =&gt; amount &gt; 0)\r\n  .ToArray(); \/\/ Возврвщаемся к синхронной модели<\/code><\/pre><h2>Асинхронное программирование<\/h2>\n<p>Мы запускаем какую-то задачу, но не ждем ответа, а продолжаем делать свою работу. А когда будет готов ответ — нас уведомят. Обычно такие операции бывают при работе с сетью, диском или любыми другими продолжительными задачами.<\/p>\n<p>Пример на C#.<br \/>\nУ нас есть продолжительная асинхронная задача, которая обращается к БД. С помощью конструкций async\/await мы организовываем асинхронную работу. Пока БД готовит для нас ответ, поток, который обслуживал этот метод возвращается в пулл потоков и может тем временем выполнять полезную работу. Как только БД отдаст ответ, нашему методу снова выделяется поток и продолжается работа.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var asyncResult = await Database.GetAllUsers(); \/\/ длительная асинхронная операция\r\nvar activeUsers = asyncResult.Where(user =&gt; user.IsActive).ToList(); \/\/ работаем с результатом асинхронной операции<\/code><\/pre><h2>Еще один забавный но наглядный пример<\/h2>\n<p>Вам нужно выкопать во дворе бассейн.<\/p>\n<ol start=\"1\">\n<li>Вы взяли лопату и копаете. Это однопоточная работа<\/li>\n<li>Вы пригласили друга Васю и копаете вместе, периодически задевая друг-друга лопатами. Это многопоточная работа<\/li>\n<li>Пока вы копаете бассейн, Вася копает канаву под водопровод. Никто никому не мешает. Это распараллеливание<\/li>\n<li>Вы пригласили бригаду землекопов, а сами с Васей пошли пить пиво. Когда бригада все сделает, к вам придут за деньгами. Это асинхронная работа.<\/li>\n<\/ol>\n<p>Количество лопат в хозяйстве — это количество ядер в системе<\/p>\n<h2>Ссылки<\/h2>\n<p>— <a href=\"https:\/\/habr.com\/ru\/post\/337528\/\">Параллелизм против многопоточности против асинхронного программирования: разъяснение<\/a><br \/>\n— <a href=\"https:\/\/agladky.ru\/blog\/async-await-example\/\">Cтатья об async\/await в C#<\/a><br \/>\n— <a href=\"https:\/\/ru.stackoverflow.com\/questions\/445768\/%d0%9c%d0%bd%d0%be%d0%b3%d0%be%d0%bf%d0%be%d1%82%d0%be%d1%87%d0%bd%d0%be%d0%b5-vs-%d0%b0%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%be%d0%b5-%d0%bf%d1%80%d0%be%d0%b3%d1%80%d0%b0%d0%bc%d0%bc%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5\">Многопоточное vs асинхронное программирование (stackoverflow) <\/a><\/p>\n",
            "date_published": "2019-11-03T15:42:51-04:00",
            "date_modified": "2019-10-29T17:13:54-04:00",
            "tags": [
                "dotnet",
                "програмування"
            ],
            "_date_published_rfc2822": "Sun, 03 Nov 2019 15:42:51 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "161",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "134",
            "url": "https:\/\/www.stefaniuk.website\/all\/linq-cheetscheet\/",
            "title": "Шпаргалка по LINQ",
            "content_html": "<div style=\"max-width: 800px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/LINQ-result-methods-1.png\" width=\"1500\" height=\"1583\" alt=\"\" \/>\n<\/div>\n<\/div><div style=\"max-width: 800px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/LINQ-query-methods.png\" width=\"1500\" height=\"1782\" alt=\"\" \/>\n<\/div>\n<\/div>",
            "date_published": "2019-11-02T19:35:18-04:00",
            "date_modified": "2023-06-02T20:01:41-04:00",
            "tags": [
                "dotnet",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/LINQ-result-methods-1.png",
            "_date_published_rfc2822": "Sat, 02 Nov 2019 19:35:18 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "134",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/LINQ-result-methods-1.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/LINQ-query-methods.png"
                ]
            }
        },
        {
            "id": "158",
            "url": "https:\/\/www.stefaniuk.website\/all\/luchshee-obyasneniya-raboty-semafora\/",
            "title": "Лучшее объяснения работы семафора",
            "content_html": "<p>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.<\/p>\n",
            "date_published": "2019-10-21T15:39:32-04:00",
            "date_modified": "2019-10-21T15:39:26-04:00",
            "tags": [
                "dotnet",
                "програмування"
            ],
            "_date_published_rfc2822": "Mon, 21 Oct 2019 15:39:32 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "158",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "69",
            "url": "https:\/\/www.stefaniuk.website\/all\/vscode-with-dotnet\/",
            "title": "Разработка на C# с помощью Visual Studio Code",
            "content_html": "<p>Пару недель назад решил перейти на VS Code в качестве основной IDE для .NET приложений. Составил список плагинов, которые использую каждый день.<\/p>\n<ol start=\"1\">\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ms-vscode.csharp\">C#<\/a><\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=fernandoescolar.vscode-solution-explorer\">vscode-solution-explorer<\/a> — позволяет работать с файлами .sln. Добавляет для этого дополнительное меню.<\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=VisualStudioExptTeam.vscodeintellicode\">Visual Studio IntelliCode<\/a><\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ms-vscode.PowerShell\">PowerShell<\/a> — отличное расширение, которое полностью заменяет PowerShell ISE.<\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=eamodio.gitlens\">GitLens — Git supercharged<\/a><\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=streetsidesoftware.code-spell-checker\">Code Spell Checker<\/a><\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=jchannon.csharpextensions\">C# Extension<\/a><\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=formulahendry.dotnet-test-explorer\">.NET Core Test Explorer<\/a><\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=alefragnani.Bookmarks\">Bookmarks<\/a><\/li>\n<\/ol>\n",
            "date_published": "2019-06-06T11:57:55-04:00",
            "date_modified": "2019-06-06T11:57:33-04:00",
            "tags": [
                "dotnet",
                "інструменти",
                "програмування"
            ],
            "_date_published_rfc2822": "Thu, 06 Jun 2019 11:57:55 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "69",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "66",
            "url": "https:\/\/www.stefaniuk.website\/all\/cancellationtoken\/",
            "title": "CancellationToken в C#",
            "content_html": "<p>CancellationToken являются частью механизма отмены асинхронных операций. Например пользователь сидит на сайте и делает запрос на какой-то сложный ресурс. Не дожидаясь ответа он закрывает вкладку браузера, который в свою очередь отменяет запрос. Но наш код не знает что запрос отменен и можно не делать лишнюю работу. А теперь представим что пользователь заходит на сайт и 5 раз перегружает страницу, теперь сервер 5 раз выполняет одну и ту же работу.<\/p>\n<p>Чтобы решить эту проблему нужно использовать механизм отмены задач. Состоит он из двух компонентов:<\/p>\n<ul>\n<li>CancellationTokenSource — позволяет создавать специальные токены, который содержат информацию про состояние текущей отмены.<\/li>\n<li>CancellationToken — собственно сам токен, у него есть свойство IsCancellationRequested, которое показывает состояние отмены, а также есть метод, который выбрасывает исключение (OperationCanceledException) в случае отмены операции.<\/li>\n<\/ul>\n<h2>Как это работает?<\/h2>\n<p>Большенство методов стандартной библиотеки уже имеют перегрузку, которая принимает токен. Чтобы его создать, нужно сначала создать фабрику CancellationTokenSource. Дальше с ее помощью сгенерировать токен и передать его в качестве аргумента в наш асинхронный метод.<\/p>\n<p>Чтобы отменить задачу нужно вызвать метод Cancel у экземпляра CancellationTokenSource. Он переведет все выпущенные токены в отмененное состояние. Внутри асинхронного метода сработает проверка токена и работа будет завершена.<\/p>\n<p>Также CancellationTokenSource позволяет установить таймаут по истечению которого автоматически отменяться все токены.<\/p>\n<p>Простой пример реализации метода, который принимает токен и проверяет его состояние.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">public static void Main() \r\n{\r\n    CancellationTokenSource cancelTokenSource = new CancellationTokenSource();\r\n    CancellationToken token = cancelTokenSource.Token;\r\n    \r\n    var task = Task.Run(() =&gt; Task(cancelTokenSource.Token), cancelTokenSource.Token);\r\n\r\n    cancelTokenSource.Cancel();\r\n}\r\n\r\npublic static void Task(CancellationToken cancellationToken) \r\n{\r\n    while (true) \r\n    {\r\n        \/\/ делает полезную работу.\r\n\r\n        cancellationToken.ThrowIfCancellationRequested();\r\n    }\r\n    \r\n}<\/code><\/pre><h2>Советы<\/h2>\n<ul>\n<li>В ASP.NET Core приложениях стоит использовать токены отмены для эндпоинтов, которые долго выполняются или сильно нагружают систему. Это позволит отменять такие операции в случае тайм-аутов или когда закрылось подключение.<\/li>\n<li>Если операция может быть отменена сразу же без дополнительных действий то для проверки отмены стоит использовать ThrowIfCancellationRequested. Если же нам нужно после вызова отмены сделать некую дополнительную работу (очистить ресурсы), то стоит проверять состояние с помощью свойства IsCancellationRequested и если оно равно true — выполнить дополнительную логику очистки.<\/li>\n<li>Если метод уже выполнил свою работу и была вызвана отмена, то не нужно бросать исключение OperationCanceledException. Нужно просто вернуть результат работы и пускай вызывающий код решать что делать с этим результатом.<\/li>\n<li>Можно вообще обойтись без использования механизма отмены если код отрабатывает очень быстро.<\/li>\n<\/ul>\n<p>Полезные ссылки по теме:<\/p>\n<ul>\n<li><a href=\"https:\/\/stackoverflow.com\/questions\/19010856\/should-we-use-cancellationtoken-with-mvc-web-api-controllers\">Should we use CancellationToken with MVC\/Web API controllers?<\/a><\/li>\n<li><a href=\"https:\/\/habr.com\/ru\/post\/168669\/\">Задачи и отмена в .Net — tips & tricks<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/premier-developer\/recommended-patterns-for-cancellationtoken\/\">Recommended patterns for CancellationToken<\/a><\/li>\n<\/ul>\n",
            "date_published": "2019-05-27T06:26:41-04:00",
            "date_modified": "2022-05-01T09:57:04-04:00",
            "tags": [
                "asp.net core",
                "dotnet",
                "програмування"
            ],
            "_date_published_rfc2822": "Mon, 27 May 2019 06:26:41 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "66",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "15",
            "url": "https:\/\/www.stefaniuk.website\/all\/best-practices-for-building-async-apis-with-asp-net-core\/",
            "title": "Best Practices for Building Async APIs with ASP.NET Core",
            "content_html": "<p>На днях был вебинар от JetBrains на тему: «Лучшие практики по построению асинхронных API в ASP.NET Core».<br \/>\nВ скором времени опубликую заметку на эту тему.<\/p>\n<div class=\"e2-text-video\">\n<iframe src=\"https:\/\/www.youtube.com\/embed\/_T3kvAxAPpQ?enablejsapi=1\" allow=\"autoplay\" frameborder=\"0\" allowfullscreen><\/iframe>\n<\/div>\n",
            "date_published": "2019-01-25T06:41:54-04:00",
            "date_modified": "2020-12-26T16:31:46-04:00",
            "tags": [
                "asp.net core",
                "dotnet",
                "видео"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/remote\/youtube-_T3kvAxAPpQ-cover.jpg",
            "_date_published_rfc2822": "Fri, 25 Jan 2019 06:41:54 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "15",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/jquery\/jquery.js",
                    "system\/library\/media-seek\/media-seek.js"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/remote\/youtube-_T3kvAxAPpQ-cover.jpg"
                ]
            }
        },
        {
            "id": "6",
            "url": "https:\/\/www.stefaniuk.website\/all\/tools-for-analyze-application-dump\/",
            "title": "Инструменты для анализа дампа приложения",
            "content_html": "<p>Иногда наступает ситуация, когда нужно проанализировать дамп приложения. Но почти никто не знает что с ним в дальнейшем делать и какие есть инструменты для работы с ним. В данной заметке я хочу поделится своим набором инструментов.<\/p>\n<h2>Инструменты<\/h2>\n<ol start=\"1\">\n<li><a href=\"https:\/\/github.com\/Microsoft\/perfview\">PerfView<\/a> — открытое решение от компании Microsoft для анализа производительности.<\/li>\n<li><a href=\"https:\/\/www.jetbrains.com\/dotmemory\/\">dotMemory<\/a> — платное решение от компании JetBrains. Привлекает тем что не только умеет анализировать дампы, но и многое другое. Также имеет красивый и удобный GUI<\/li>\n<li><a href=\"https:\/\/github.com\/fremag\/MemoScope.Net\">MemoScope<\/a> — открытое решение от независимого разработчика.<\/li>\n<li>Visual Studio —  имеет встроенный функционал по анализу дампов приложения.<\/li>\n<\/ol>\n<h2>Ссылки<\/h2>\n<p><b>PerfView<\/b><\/p>\n<ol start=\"1\">\n<li><a href=\"https:\/\/blogs.msdn.microsoft.com\/vancem\/tag\/perfview\/\">Блог MSDN по тегу PerfView<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=eX644hod65s\"> Video: PerfView: Measure and Improve Your App’s Performance For Free<\/a><\/li>\n<li><a href=\"https:\/\/channel9.msdn.com\/Series\/PerfView-Tutorial\">Video: Video tutorial by Chanel 9<\/a><\/li>\n<li><a href=\"https:\/\/blogs.msdn.microsoft.com\/dotnet\/2012\/10\/09\/improving-your-apps-performance-with-perfview\/\">Improving Your App’s Performance with PerfView<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=qGEeZZBwVp4\">Video: PerfView: The Ultimate .NET Performance Tool<\/a><\/li>\n<\/ol>\n<p><b>MemoScope<\/b><\/p>\n<ol start=\"1\">\n<li><a href=\"https:\/\/github.com\/fremag\/MemoScope.Net\/wiki\">Официальная Wiki<\/a><\/li>\n<\/ol>\n<p><b>Visual Studio<\/b><\/p>\n<ol start=\"1\">\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=lU_YZtvslnI\">Video: Debugging Memory Leaks Using New NET Memory Diagnostic Tools<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/debugger\/using-dump-files?view=vs-2017\">Visual Studio Docs<\/a><\/li>\n<\/ol>\n<p><b>DotMemory<\/b><\/p>\n<ol start=\"1\">\n<li><a href=\"https:\/\/www.jetbrains.com\/help\/dotmemory\/Examples.html\">Official Page<\/a><\/li>\n<\/ol>\n",
            "date_published": "2018-12-13T12:27:23-04:00",
            "date_modified": "2023-06-02T20:23:05-04:00",
            "tags": [
                "dotnet",
                "інструменти"
            ],
            "_date_published_rfc2822": "Thu, 13 Dec 2018 12:27:23 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "6",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "4",
            "url": "https:\/\/www.stefaniuk.website\/all\/debug-vs-release-builds-when-debbuging\/",
            "title": "C# Debug vs Release. Сборки и дебаг",
            "content_html": "<p>Оригинальная статья: <a href=\"http:\/\/benhall.io\/c-debug-vs-release-builds-and-debugging-in-visual-studio-from-novice-to-expert-in-one-blog-article\/?utm_campaign=dotNET%20Weekly&utm_medium=email&utm_source=week-21_year-2018\">тык<\/a>.<\/p>\n<p>Из коробки в C# нам доступны 2 способа сборки проекта release и debug.<\/p>\n<h2>О компиляции C# кода<\/h2>\n<p>Исходный код C # проходит через 2 этапа компиляции, чтобы стать инструкциями CPU, которые могут быть выполнены.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/1.png\" width=\"606\" height=\"223\" alt=\"\" \/>\n<\/div>\n<p>Обычно первый этап происходит на вашем CI сервере, а второй шаг происходит позже, во время работы самого приложения. Когда же мы работаем локально в Visual Studio, то она все эти шаги выполняет перед запуском приложения из меню Debug.<\/p>\n<p><b>Шаг первый. Компиляция приложения<\/b><br \/>\nВаш код превращается в Common Intermediate Language (CIL), который уже может быть выполнен в любом окружении, которое поддерживает CIL. Обратите внимание, что собранная сборка не является читаемым текстом IL, а фактически метаданными и байтовым кодом в виде двоичных данных.<\/p>\n<p>На данном шаге будет выполнена некоторая оптимизация кода (будет описано дальше).<\/p>\n<p><b>Шаг второй. JIT компилятор<\/b><br \/>\nJIT компилятор конвертирует IL код в инструкции процессора, которые можно выполнить на вашей машине. Однако не вся компиляция происходит заранее — в нормальном режиме, код компилируется, только тогда когда его вызывают в первый раз, после чего он кэшируется.<\/p>\n<p>Компилятор JIT — это всего лишь один из целого ряда сервисов, которые составляют Common Language Runtime (CLR), позволяя ему выполнять код .NET.<\/p>\n<p>Основная часть оптимизации кода будет проведена на этом шаге.<\/p>\n<h2>Что такое оптимизация кода в одном предложении?<\/h2>\n<p>Это процесс улучшения таких факторов, как скорость выполнения, размер кода, энергопотребление, а в случае .NET — время, которое требуется для компилятора JIT — без изменения функциональности.<\/p>\n<h2>Почему мы заинтересованы в оптимизации в этой статье?<\/h2>\n<p>На обоих этапах компиляции ваш код будет оптимизирован компиляторами. Одно из ключевых различий между конфигурациями сборки Debug и Release заключается в том, отключены ли оптимизации или нет, поэтому нам нужно понять последствия данных оптимизации.<\/p>\n<h2>Оптимизация компилятора C#<\/h2>\n<p>C# компилятор на самом деле делает очень мало оптимизаций. На самом деле большенство оптимизаций производит JIT компилятор во время генерирования машинного кода. Тем не менее это все равно ухудшит работу по отладке.<\/p>\n<p><b>Инструкция nop в IL<\/b><br \/>\nКоманда nop имеет ряд применений при программировании на низком уровне, например, для включения небольших предсказуемых задержек или инструкции по перезаписи, которые вы хотите удалить. В IL коде данные конструкции помогают при использовании точек останова (breakpoints) для того чтобы они вели себя предсказуемо.<\/p>\n<p>Если мы посмотрим на IL код, который сгенерирован с отключенными оптимизациями:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/2.png\" width=\"314\" height=\"31\" alt=\"\" \/>\n<\/div>\n<p>Эта инструкция мапиться с фигурной скобкой, для того чтобы мы могли поставить на нее точку остановки:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/3.png\" width=\"386\" height=\"32\" alt=\"\" \/>\n<\/div>\n<p>Даная инструкция была бы удалена если у нас включены оптимизации, что повлияло бы на отладку приложения.<\/p>\n<p>Более подробное обсуждение оптимизаций компилятора C#  в статье Эрика Липперта: <a href=\"https:\/\/blogs.msdn.microsoft.com\/ericlippert\/2009\/06\/11\/what-does-the-optimize-switch-do\">Что делает переключатель оптимизации?<\/a>. Существует также хороший комментарий о IL до и после оптимизации <a href=\"https:\/\/stackoverflow.com\/questions\/21438751\/c-sharp-compiler-optimizations?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa\">здесь<\/a>.<\/p>\n<h2>Оптимизация JIT компилятора<\/h2>\n<p>Несмотря на то, что он быстро выполняет свою работу во время выполнения, компилятор JIT также выполняет множество оптимизаций. О его внутренних деталях мало информации. Даже во время работы вашего приложения он производит профилирование и, возможно, перекомпилирование кода для повышения производительности.<br \/>\nХорошие примеры оптимизации с помощью JIT компилятора можете посмотреть <a href=\"https:\/\/www.codeproject.com\/Articles\/25801\/JIT-Optimizations\">здесь<\/a>.<\/p>\n<p>Я рассмотрю один пример, чтобы проиллюстрировать влияние оптимизации на отладку.<br \/>\n<b>Встраивание методов<\/b><\/p>\n<p>Для реальной оптимизации, сделанной компилятором JIT, я буду показывать инструкции по сборке. Это всего лишь макет на C #, чтобы дать вам общую идею.<\/p>\n<p>Предположим, что у меня есть:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">private long Add(int a, int b)\r\n{\r\n    return a + b;\r\n}\r\npublic void MethodA()\r\n{\r\n    var r = Add(a, b);\r\n}<\/code><\/pre><p>Компилятор JIT, скорее всего, выполнит встроенное расширение. Он заменит вызов метода Add() телом данного метода:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">public void MethodA()\r\n{\r\n    var r = a + b;\r\n}<\/code><\/pre><h2>Конфигурации сборки по умолчанию<\/h2>\n<p>Итак, теперь, когда мы обновили понимание компиляции .NET и двух «слоев» оптимизации, давайте взглянем на 2 конфигурации сборки, доступные «из коробки»:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/4.png\" width=\"682\" height=\"268\" alt=\"\" \/>\n<\/div>\n<p>Довольно просто — релиз полностью оптимизирован, отладка совсем отсутствует, что, как вы сейчас знаете, имеет фундаментальное значение для того, насколько легко отлаживать ваш код. Но это просто поверхностное представление о возможностях аргументов отладки и оптимизации.<\/p>\n<h2>Внутренности аргументов оптимизации и отладки<\/h2>\n<p>Я попытался продемонстрировать данные аргументы из кода Roslyn и mscorlib. Теперь мы имеем следующие классы:<\/p>\n<ol start=\"1\">\n<li><a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/master\/src\/Compilers\/CSharp\/Portable\/CommandLine\/CSharpCommandLineParser.cs\">CSharpCommandLineParser<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/9b8aa38370fe6ad4afa10add7fa5a5bac22a9355\/src\/Compilers\/CSharp\/Portable\/CodeGen\/CodeGenerator.cs\">CodeGenerator<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/master\/src\/Compilers\/Core\/Portable\/CodeGen\/ILEmitStyle.cs\">ILEmitStyle<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Microsoft\/referencesource\/blob\/master\/mscorlib\/system\/diagnostics\/debuggerattributes.cs\">debuggerattributes<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/ef41d39c55a63d9464cc4eb5344be4d8df6408d8\/src\/Compilers\/CSharp\/Portable\/CodeGen\/Optimizer.cs\">Optimizer<\/a><\/li>\n<li><a href=\"http:\/\/source.roslyn.io\/#Microsoft.CodeAnalysis\/Compilation\/OptimizationLevel.cs,faae3325158ce920\">OptimizationLevel<\/a><\/li>\n<\/ol>\n<p>Синие элементы обозначаю аргументы командной строки, а зеленые их представление в коде.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/5.png\" width=\"605\" height=\"377\" alt=\"\" \/>\n<\/div>\n<h2>Перечисление OptimizationLevel<\/h2>\n<p><b>OptimizationLevel.Debug<\/b> отключает все оптимизации для C# и JIT компилятора с помощью DebuggableAttribute.DebuggingModes, который с помощью ildasm, мы можем видеть:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/6.png\" width=\"637\" height=\"31\" alt=\"\" \/>\n<\/div>\n<p><b>OptimizationLevel.Release<\/b> включает все оптимизации (DebuggableAttribute.DebuggingModes = ( 01 00 02 00 00 00 00 00 )) что в свою очередь соответсвует DebuggingModes.IgnoreSymbolStoreSequencePoints<\/p>\n<p>При этом уровне оптимизации точки останова могут быть оптимизированы. Что приведет к тому что мы не сможем их поставить и остановиться.<\/p>\n<h2>Типы IL<\/h2>\n<p>Типы IL кода описаны в классе <a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/master\/src\/Compilers\/Core\/Portable\/CodeGen\/ILEmitStyle.cs\">ILEmitStyle<\/a>.<\/p>\n<p>На диаграмме выше показано, что тип генерируемого IL кода C# компилятором зависит от OptimizationLevel.<br \/>\nАргумент <i>debug<\/i> не меняет его, за исключение аргумента <i>debug+<\/i> когда OptimizationLevel установлен в Release.<\/p>\n<ul>\n<li>ILEmitStyle.Debug — нету оптимизация IL в дополнение к добавлению nop инструкций для сопоставления точекостановки с IL.<\/li>\n<li>LEmitStyle.Release — полная оптимизация.<\/li>\n<li>ILEmitStyle.DebugFriendlyRelease — выполняет только те оптимизации, которые не помешаю отладке приложения.<\/li>\n<\/ul>\n<p>Следующий кусок кода продемонстрирует все это наглядно.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">if(optimizations == OptimizationLevel.Debug)\r\n{\r\n    _ilEmitStyle = ILEmitStyle.Debug;\r\n}\r\nelse\r\n{\r\n    _ilEmitStyle = IsDebugPlus() ?\r\n    ILEmitStyle.DebugFriendlyRelease :\r\n    ILEmitStyle.Release;\r\n}<\/code><\/pre><p>Комментарий в исходном файле Optimizer.cs гласит, что они не опускают никаких определенных  пользователем локальных переменных (примеры на 28 строчке) и не переносят значения в стек между операторами.<\/p>\n<p>Я рад, что прочитал это, так как я был немного разочарован своими собственными экспериментами в ildasm с debug +, поскольку все, что я видел, это сохранение локальных переменных.<\/p>\n<p>Нет намеренной «деоптимизации», например, добавления команд nop.<\/p>\n<h2>Разница между debug, debug:full и debug:pdbonly.<\/h2>\n<p>На самом деле разницы никакой нету, что подтверждает официальная документация:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/7.png\" width=\"473\" height=\"68\" alt=\"\" \/>\n<\/div>\n<p>Результат остается одним и тем же — pdb файл создается.<br \/>\nПодгланув в <a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/master\/src\/Compilers\/CSharp\/Portable\/CommandLine\/CSharpCommandLineParser.cs\">CSharpCommandLineParser<\/a> можем убедиться в этом. И для того чтобы проверить это, мне удалось отладить код с помощью WinDbg для обох аргументов (pdbonly и full).<\/p>\n<p>Они не влияют на оптимизацию кода.<br \/>\nКак плюсом могу отметить что <a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/master\/docs\/compilers\/CSharp\/CommandLine.md\">документация на Github<\/a> более актуальна, но она не открывает свет на специфическое поведение для <i>debug+<\/i><\/p>\n<p>А что же такое этот ваш pdb файл?<br \/>\nВсе очень просто, данный файл содержит всю необходимую информацию для отладки DLL и EXE. Что помогает сопоставить отладчику IL код с инструкциями в оригинальном C# коде.<\/p>\n<h2>Что на счет debug+?<\/h2>\n<p>debug+ это особенный аргумент, который не может быть заменен с помощью <i>full<\/i> и <i>pdbonly<\/i>. Как многие заметили, данный аргумент соответствует <i>debug:full<\/i> — это на самом деле не совсем правда. Если мы используем аргумент <i>optimize-<\/i>, поведение будет таким же, но для <i>optimize+<\/i> будет свое собственное уникальное поведение (DebugFriendlyRelease).<\/p>\n<h2>debug- или без аргументов?<\/h2>\n<p>Значения по умолчанию, которые установлены в <a href=\"https:\/\/github.com\/dotnet\/roslyn\/blob\/master\/src\/Compilers\/CSharp\/Portable\/CommandLine\/CSharpCommandLineParser.cs\">CSharpCommandLineParser.cs<\/a>.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">bool debugPlus = false;\r\nbool emitPdb = false;<\/code><\/pre><p>а значения для <i>debug=<\/i>:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">case &quot;debug-&quot;:\r\n    if (value != null)\r\n        break;\r\n \r\n    bool emitPdb = false;\r\n    bool debugPlus = false;<\/code><\/pre><p>Таким образом, мы можем с уверенностью сказать, что debug- и отсутствие аргументов отладки, приводет к одному и тому же эффекту — pdb файл не будет создан.<\/p>\n<h2>Запрет оптимизации JIT при загрузке модуля (Suppress JIT optimizations)<\/h2>\n<p>Флажок в разделе «Options->Debugging->General»  это опция для отладчика в Visual Studio и не повлияет на сборки, которые вы создаете.<\/p>\n<p>Вы должны теперь оценить, что компилятор JIT делает большую часть значительных оптимизаций и является большим препятствием для сопоставления исходного исходного кода для отладки. Если этот параметр включен, тогда он запросит DisableOptimizations.<\/p>\n<p>Обычно этот параметр включаю для того чтобы отладить внешние библиотеки или пакеты NuGet.<\/p>\n<p>Если мы хотим подключиться отладчиком Visual Studion к продакшен сборке, которая собрана в релизной конфигурации, то при наличии у нас pdb файла, мы может еще одним способом указать JIT компилятору, о том чтобы он не оптимизировал код. Для этого нужно добавить .ini файл с таким же названием как выполняемая библиотека и указать в нем:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">[.NET Framework Debugging Control]\r\nAllowOptimize=0<\/code><\/pre><h2>Что такое Just My Code?<\/h2>\n<p>По умолчанию эта настройка уже включена (Options->Debugging→Enable Just My Code) и отладчик думает что оптимизированный код не является пользовательским. Поэтому отладчик никогда не зайдет в такой код.<\/p>\n<p>Вы можете отключить данный флаг и это, теоретически, позволит вам поставить точку остановки. Но теперь вы отлаживаете код, оптимизированный как компиляторами C# так и JIT, который едва соответствуют исходному коду. В результате у вас будут такие артефакты отладки, как непредсказуемые переходы в коде и скорее все вам не получиться получить значения в локальных переменных.<\/p>\n<p>Этот параметр стоит отключать в том случае, когда у вас есть pdb файл.<\/p>\n<h2>Взглянем ближе на DebuggableAttribute<\/h2>\n<p>Выше я упомянул использование ildasm для изучения манифеста сборок для изучения DebuggableAttribute. Я также написал небольшой PowerShell скрипт для получения более дружественного результата (<a href=\"https:\/\/github.com\/benbhall\/DotNetGetDebuggableAttribute\">ссылка на скачивание<\/a>).<\/p>\n<p>Сборка Debug:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/8.png\" width=\"528\" height=\"70\" alt=\"\" \/>\n<\/div>\n<p>Сборка Release:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/9.png\" width=\"527\" height=\"64\" alt=\"\" \/>\n<\/div>\n<p>Вы можете игнорировать IsJITTrackingEnabled, поскольку он был проигнорирован компилятором JIT с .NET 2.0.<br \/>\nКомпилятор JIT всегда будет генерировать информацию для отслеживания во время отладки, чтобы сопоставлять IL с машинным кодом и отслеживать, где хранятся локальные переменные и аргументы функции.<\/p>\n<p><b>IsJITOptimizerDisabled<\/b> — просто проверяет DebuggingFlags на наявность DebuggingModes.DisableOptimizations. Он отвечает за включение оптимизация с помощью JIT.<\/p>\n<p><b>DebuggingModes.IgnoreSymbolStoreSequencePoints<\/b> — говорит отладчику выработать точки последовательности из IL кода вместо загрузки .pdb файла. Точки последовательности используются для сопоставления местоположений в коде IL с местоположениями в исходном коде C#. Если он включен, то JIT не будет загружать .pdb файл. Я не уверен, почему этот флаг добавляется в оптимизированные сборки компилятором C#.<br \/>\nТакже об этом флаге можно почитать <a href=\"https:\/\/blogs.msdn.microsoft.com\/rmbyers\/2005\/09\/08\/debuggingmodes-ignoresymbolstoresequencepoints\/\">здесь<\/a><\/p>\n<h2>Ключевые понятия<\/h2>\n<ol start=\"1\">\n<li><i>debug-<\/i> или отсутствие аргумента приводит к тому что не создается .pdb файл.<\/li>\n<li><i>debug<\/i>, <i>debug:full<\/i> и <i>debug:pdbonly<\/i> приводят к созданию .pdb файла. <i>debug+<\/i> делает тоже самое в случае когда установлен флаг <i>optimize-<\/i>.<\/li>\n<li><i>debug+<\/i> и <i>optimize+<\/i> создают такой IL код, который легче отлаживать.<\/li>\n<li>Каждый слой оптимизации ухудшает отладку кода.<\/li>\n<li>С .NET 2.0 компилятор JIT всегда будет генерировать информацию для отслеживания независимо от атрибута IsJITTrackingEnabled.<\/li>\n<li>Будь то сборка через VS или csc.exe, атрибут DebuggableAttribute теперь всегда присутствует.<\/li>\n<li><i>optimised+<\/i> создает бинарники, которые отладчик воспринимает как сторонний код. Это поведение управляется с помощью опции Just My Code, но при ее отключении вы можете получить очень сомнительный опыт отладки.<\/li>\n<\/ol>\n<h2>Теперь у вас есть выбор:<\/h2>\n<ol start=\"1\">\n<li>Debug: debug|debug:full|debug:pdbonly optimize+<\/li>\n<li>Release: debug-|no debug argument optimize+<\/li>\n<li>DebugFriendlyRelease: debug+ optimize+<\/li>\n<\/ol>\n",
            "date_published": "2018-11-24T19:52:16-04:00",
            "date_modified": "2019-02-26T09:15:57-04:00",
            "tags": [
                "dotnet",
                "перевод"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/1.png",
            "_date_published_rfc2822": "Sat, 24 Nov 2018 19:52:16 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "4",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/1.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/2.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/3.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/4.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/5.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/6.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/7.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/8.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/9.png"
                ]
            }
        }
    ],
    "_e2_version": 4116,
    "_e2_ua_string": "Aegea 11.2 (v4116)"
}