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

.net

Parallel, Asynchronous, Multithreading programming

Многопоточное программирование

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

Параллельное программирование

Подразумевает что некоторая задача разбивается на несколько независимых подзадач, которые можно выполнить параллельно и потом объединить результаты.
Примером такой задачи может быть Parallel LINQ

IEnumerable<Data> yourData = GetYourData();
var result = yourData.AsParallel() // начинаем обрабатывать параллельно
  .Select(d => d.CalcAmount()) // Вычисляем параллельно
  .Where(amount => amount > 0)
  .ToArray(); // Возврвщаемся к синхронной модели

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

Мы запускаем какую-то задачу, но не ждем ответа, а продолжаем делать свою работу. А когда будет готов ответ — нас уведомят. Обычно такие операции бывают при работе с сетью, диском или любыми другими продолжительными задачами.

Пример на C#.
У нас есть продолжительная асинхронная задача, которая обращается к БД. С помощью конструкций async/await мы организовываем асинхронную работу. Пока БД готовит для нас ответ, поток, который обслуживал этот метод возвращается в пулл потоков и может тем временем выполнять полезную работу. Как только БД отдаст ответ, нашему методу снова выделяется поток и продолжается работа.

var asyncResult = await Database.GetAllUsers(); // длительная асинхронная операция
var activeUsers = asyncResult.Where(user => user.IsActive).ToList(); // работаем с результатом асинхронной операции

Еще один забавный но наглядный пример

Вам нужно выкопать во дворе бассейн.

  1. Вы взяли лопату и копаете. Это однопоточная работа
  2. Вы пригласили друга Васю и копаете вместе, периодически задевая друг-друга лопатами. Это многопоточная работа
  3. Пока вы копаете бассейн, Вася копает канаву под водопровод. Никто никому не мешает. Это распараллеливание
  4. Вы пригласили бригаду землекопов, а сами с Васей пошли пить пиво. Когда бригада все сделает, к вам придут за деньгами. Это асинхронная работа.

Количество лопат в хозяйстве — это количество ядер в системе

Ссылки

Параллелизм против многопоточности против асинхронного программирования: разъяснение
Cтатья об async/await в C#
Многопоточное vs асинхронное программирование (stackoverflow) 

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

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.

Хеш-таблица

Хеш-таблица — специальная структура данных, в которой данные хранятся в виде хеш — значение. Очень похожа на словарь, но в ее основе используется хеш функция для ускорения поиска.

Есть два основных способа реализации хеш-таблицы:

  1. Метода цепочек — данные с одинаковым хешем обьединяються в список.
  2. Метод открытой адресации — если при добавлении данных ячейка занята, то переходим к следующей, до тех пор пока не найдем свободную.

В .NET уже существует готовая реализация этой структуры: System.Collections.HasTable. Но с появлением обобщенных коллекций стал устаревшим. На его место пришел словарь — Dictionary. Так как в базовом объекте есть 2 метода Equal и GetHashCode мы можем в качестве ключа словаря использовать любой тип данных.

Коллекции в .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# с помощью 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.

Источники:

  1. Задачи и отмена в .Net — tips & tricks
  2. Отмена задач и параллельных операций. CancellationToken
 Нет комментариев    49   6 мес   .net   c#

Инструменты для анализа дампа приложения

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

Инструменты

  1. PerfView — открытое решение от компании Microsoft для анализа производительности.
  2. dotMemory — платное решение от компании JetBrains. Привлекает тем что не только умеет анализировать дампы, но и многое другое. Также имеет красивый и удобный GUI
  3. MemoScope — открытое решение от независимого разработчика.
  4. Visual Studio — имеет встроенный функционал по анализу дампов приложения.

Ссылки

PerfView

  1. Блог MSDN по тегу PerfView
  2. Video: PerfView: Measure and Improve Your App’s Performance For Free
  3. Video: Video tutorial by Chanel 9
  4. Improving Your App’s Performance with PerfView
  5. Video: PerfView: The Ultimate .NET Performance Tool

MemoScope

  1. Официальная Wiki

Visual Studio

  1. Video: Debugging Memory Leaks Using New NET Memory Diagnostic Tools
  2. Visual Studio Docs

DotMemory

  1. Official Page
 Нет комментариев    77   12 мес   .net   performance   profiling

Кратко о WebSocket

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

Основной библиотекой для упрощения работы с сокетами от Microsoft является SignalR. Она берет на себя всю работу с данным протоколом и дает разработчику удобный интерфейс для работы.

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

SignalR предоставляет разработчикам две модели: постоянные подключения (Persistent Connection) и хабы (Hubs).
Постоянные подключения (Persistent Connection API) представляют разработчикам прямой доступ к низкоуровневому протоколу коммуникации. Подключения в этой модели представляют конечную точку, к которой подключаются клиенты, наподобие модели подключений в WCF.
Хабы же предоставляют протокол взаимодействия более высокого уровня. Они представляют верхний слой над Persistent Connection API и позволяют клиенту и серверу напрямую вызывать методы друг друга.

SignalR предоставляет следующие типы технологий для взаимодействия сервера и клиента:

  • WebSockets
  • Server-sent events
  • Forever Frames
  • Long polling

Картинка, которая иллюстрирует процесс установления связи по протоколу WebSocket

Следующий код был взят из websocket.org, но он демонстрирует базовое взаимодействие со стороны клиента с данным протоколом:

var wsUri = "ws://echo.websocket.org/";
var output;

function init()
{
  output = document.getElementById("output");
  testWebSocket();
}

function testWebSocket()
{
  websocket = new WebSocket(wsUri);
  websocket.onopen = function(evt) { onOpen(evt) };
  websocket.onclose = function(evt) { onClose(evt) };
  websocket.onmessage = function(evt) { onMessage(evt) };
  websocket.onerror = function(evt) { onError(evt) };
}

function onOpen(evt)
{
  writeToScreen("CONNECTED");
  doSend("WebSocket rocks");
}

function onClose(evt)
{
  writeToScreen("DISCONNECTED");
}

function onMessage(evt)
{
  writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
  websocket.close();
}

function onError(evt)
{
  writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}

function doSend(message)
{
  writeToScreen("SENT: " + message);
  websocket.send(message);
}

function writeToScreen(message)
{
  var pre = document.createElement("p");
  pre.style.wordWrap = "break-word";
  pre.innerHTML = message;
  output.appendChild(pre);
}

window.addEventListener("load", init, false);

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

По мнению многих, WebSockets — едва ли не самое полезное изобретение после горячей воды. Когда вы ухватите суть WebSockets, вы будете удивляться, как это мир программного обеспечения мог обходиться без этих сокетов. WebSockets здорово пригодится в ряде приложений, но отнюдь не во всех. В любом приложении, где мгновенный обмен сообщениями является ключевым требованием, — имеет смысл серьезно подумать о создании WebSocket-сервера и клиентов (веб-приложения, мобильные и даже настольные программы). Еще одна группа приложений, которые получат выигрыш от внедрения WebSocket Protocol, — игры и каналы реального времени. Да, WebSockets — определенно лучшее, что было изобретено после горячей воды!

Ссылки:

  1. Отличный пример реализации клиента и сервера без использования сторонних библиотек
  2. SignalR Core. Первое приложение
  3. Офф. сайт
 Нет комментариев    46   1 год   .net   c#   скиллы

C# Debug vs Release. Сборки и дебаг

Оригинальная статья: тык.

Из коробки в C# нам доступны 2 способа сборки проекта release и debug.

О компиляции C# кода

Исходный код C # проходит через 2 этапа компиляции, чтобы стать инструкциями CPU, которые могут быть выполнены.

Обычно первый этап происходит на вашем CI сервере, а второй шаг происходит позже, во время работы самого приложения. Когда же мы работаем локально в Visual Studio, то она все эти шаги выполняет перед запуском приложения из меню Debug.

Шаг первый. Компиляция приложения
Ваш код превращается в Common Intermediate Language (CIL), который уже может быть выполнен в любом окружении, которое поддерживает CIL. Обратите внимание, что собранная сборка не является читаемым текстом IL, а фактически метаданными и байтовым кодом в виде двоичных данных.

На данном шаге будет выполнена некоторая оптимизация кода (будет описано дальше).

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

Компилятор JIT — это всего лишь один из целого ряда сервисов, которые составляют Common Language Runtime (CLR), позволяя ему выполнять код .NET.

Основная часть оптимизации кода будет проведена на этом шаге.

Что такое оптимизация кода в одном предложении?

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

Почему мы заинтересованы в оптимизации в этой статье?

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

Оптимизация компилятора C#

C# компилятор на самом деле делает очень мало оптимизаций. На самом деле большенство оптимизаций производит JIT компилятор во время генерирования машинного кода. Тем не менее это все равно ухудшит работу по отладке.

Инструкция nop в IL
Команда nop имеет ряд применений при программировании на низком уровне, например, для включения небольших предсказуемых задержек или инструкции по перезаписи, которые вы хотите удалить. В IL коде данные конструкции помогают при использовании точек останова (breakpoints) для того чтобы они вели себя предсказуемо.

Если мы посмотрим на IL код, который сгенерирован с отключенными оптимизациями:

Эта инструкция мапиться с фигурной скобкой, для того чтобы мы могли поставить на нее точку остановки:

Даная инструкция была бы удалена если у нас включены оптимизации, что повлияло бы на отладку приложения.

Более подробное обсуждение оптимизаций компилятора C# в статье Эрика Липперта: Что делает переключатель оптимизации?. Существует также хороший комментарий о IL до и после оптимизации здесь.

Оптимизация JIT компилятора

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

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

Для реальной оптимизации, сделанной компилятором JIT, я буду показывать инструкции по сборке. Это всего лишь макет на C #, чтобы дать вам общую идею.

Предположим, что у меня есть:

private long Add(int a, int b)
{
    return a + b;
}
public void MethodA()
{
    var r = Add(a, b);
}

Компилятор JIT, скорее всего, выполнит встроенное расширение. Он заменит вызов метода Add() телом данного метода:

public void MethodA()
{
    var r = a + b;
}

Конфигурации сборки по умолчанию

Итак, теперь, когда мы обновили понимание компиляции .NET и двух «слоев» оптимизации, давайте взглянем на 2 конфигурации сборки, доступные «из коробки»:

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

Внутренности аргументов оптимизации и отладки

Я попытался продемонстрировать данные аргументы из кода Roslyn и mscorlib. Теперь мы имеем следующие классы:

  1. CSharpCommandLineParser
  2. CodeGenerator
  3. ILEmitStyle
  4. debuggerattributes
  5. Optimizer
  6. OptimizationLevel

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

Перечисление OptimizationLevel

OptimizationLevel.Debug отключает все оптимизации для C# и JIT компилятора с помощью DebuggableAttribute.DebuggingModes, который с помощью ildasm, мы можем видеть:

OptimizationLevel.Release включает все оптимизации (DebuggableAttribute.DebuggingModes = ( 01 00 02 00 00 00 00 00 )) что в свою очередь соответсвует DebuggingModes.IgnoreSymbolStoreSequencePoints

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

Типы IL

Типы IL кода описаны в классе ILEmitStyle.

На диаграмме выше показано, что тип генерируемого IL кода C# компилятором зависит от OptimizationLevel.
Аргумент debug не меняет его, за исключение аргумента debug+ когда OptimizationLevel установлен в Release.

  • ILEmitStyle.Debug — нету оптимизация IL в дополнение к добавлению nop инструкций для сопоставления точекостановки с IL.
  • LEmitStyle.Release — полная оптимизация.
  • ILEmitStyle.DebugFriendlyRelease — выполняет только те оптимизации, которые не помешаю отладке приложения.

Следующий кусок кода продемонстрирует все это наглядно.

if(optimizations == OptimizationLevel.Debug)
{
    _ilEmitStyle = ILEmitStyle.Debug;
}
else
{
    _ilEmitStyle = IsDebugPlus() ?
    ILEmitStyle.DebugFriendlyRelease :
    ILEmitStyle.Release;
}

Комментарий в исходном файле Optimizer.cs гласит, что они не опускают никаких определенных пользователем локальных переменных (примеры на 28 строчке) и не переносят значения в стек между операторами.

Я рад, что прочитал это, так как я был немного разочарован своими собственными экспериментами в ildasm с debug +, поскольку все, что я видел, это сохранение локальных переменных.

Нет намеренной «деоптимизации», например, добавления команд nop.

Разница между debug, debug:full и debug:pdbonly.

На самом деле разницы никакой нету, что подтверждает официальная документация:

Результат остается одним и тем же — pdb файл создается.
Подгланув в CSharpCommandLineParser можем убедиться в этом. И для того чтобы проверить это, мне удалось отладить код с помощью WinDbg для обох аргументов (pdbonly и full).

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

А что же такое этот ваш pdb файл?
Все очень просто, данный файл содержит всю необходимую информацию для отладки DLL и EXE. Что помогает сопоставить отладчику IL код с инструкциями в оригинальном C# коде.

Что на счет debug+?

debug+ это особенный аргумент, который не может быть заменен с помощью full и pdbonly. Как многие заметили, данный аргумент соответствует debug:full — это на самом деле не совсем правда. Если мы используем аргумент optimize-, поведение будет таким же, но для optimize+ будет свое собственное уникальное поведение (DebugFriendlyRelease).

debug- или без аргументов?

Значения по умолчанию, которые установлены в CSharpCommandLineParser.cs.

bool debugPlus = false;
bool emitPdb = false;

а значения для debug=:

case "debug-":
    if (value != null)
        break;
 
    bool emitPdb = false;
    bool debugPlus = false;

Таким образом, мы можем с уверенностью сказать, что debug- и отсутствие аргументов отладки, приводет к одному и тому же эффекту — pdb файл не будет создан.

Запрет оптимизации JIT при загрузке модуля (Suppress JIT optimizations)

Флажок в разделе «Options->Debugging->General» это опция для отладчика в Visual Studio и не повлияет на сборки, которые вы создаете.

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

Обычно этот параметр включаю для того чтобы отладить внешние библиотеки или пакеты NuGet.

Если мы хотим подключиться отладчиком Visual Studion к продакшен сборке, которая собрана в релизной конфигурации, то при наличии у нас pdb файла, мы может еще одним способом указать JIT компилятору, о том чтобы он не оптимизировал код. Для этого нужно добавить .ini файл с таким же названием как выполняемая библиотека и указать в нем:

[.NET Framework Debugging Control]
AllowOptimize=0

Что такое Just My Code?

По умолчанию эта настройка уже включена (Options->Debugging→Enable Just My Code) и отладчик думает что оптимизированный код не является пользовательским. Поэтому отладчик никогда не зайдет в такой код.

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

Этот параметр стоит отключать в том случае, когда у вас есть pdb файл.

Взглянем ближе на DebuggableAttribute

Выше я упомянул использование ildasm для изучения манифеста сборок для изучения DebuggableAttribute. Я также написал небольшой PowerShell скрипт для получения более дружественного результата (ссылка на скачивание).

Сборка Debug:

Сборка Release:

Вы можете игнорировать IsJITTrackingEnabled, поскольку он был проигнорирован компилятором JIT с .NET 2.0.
Компилятор JIT всегда будет генерировать информацию для отслеживания во время отладки, чтобы сопоставлять IL с машинным кодом и отслеживать, где хранятся локальные переменные и аргументы функции.

IsJITOptimizerDisabled — просто проверяет DebuggingFlags на наявность DebuggingModes.DisableOptimizations. Он отвечает за включение оптимизация с помощью JIT.

DebuggingModes.IgnoreSymbolStoreSequencePoints — говорит отладчику выработать точки последовательности из IL кода вместо загрузки .pdb файла. Точки последовательности используются для сопоставления местоположений в коде IL с местоположениями в исходном коде C#. Если он включен, то JIT не будет загружать .pdb файл. Я не уверен, почему этот флаг добавляется в оптимизированные сборки компилятором C#.
Также об этом флаге можно почитать здесь

Ключевые понятия

  1. debug- или отсутствие аргумента приводит к тому что не создается .pdb файл.
  2. debug, debug:full и debug:pdbonly приводят к созданию .pdb файла. debug+ делает тоже самое в случае когда установлен флаг optimize-.
  3. debug+ и optimize+ создают такой IL код, который легче отлаживать.
  4. Каждый слой оптимизации ухудшает отладку кода.
  5. С .NET 2.0 компилятор JIT всегда будет генерировать информацию для отслеживания независимо от атрибута IsJITTrackingEnabled.
  6. Будь то сборка через VS или csc.exe, атрибут DebuggableAttribute теперь всегда присутствует.
  7. optimised+ создает бинарники, которые отладчик воспринимает как сторонний код. Это поведение управляется с помощью опции Just My Code, но при ее отключении вы можете получить очень сомнительный опыт отладки.

Теперь у вас есть выбор:

  1. Debug: debug|debug:full|debug:pdbonly optimize+
  2. Release: debug-|no debug argument optimize+
  3. DebugFriendlyRelease: debug+ optimize+
 Нет комментариев    222   1 год   .net   c#   перевод