Posted on 19. March 2020

Асинхронное объединение ValueTask в .NET 5

Функция async / await в C # произвела революцию в том, как разработчики, нацеленные на .NET, пишут асинхронный код. Прибавьте немного async и await, измените, некоторые типы возвращаемых данных на задачи, и вы получите асинхронную реализацию. Теоретически.

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

Распределения и конечные автоматы

Когда вы пишете асинхронный метод в C #, компилятор переписывает это в конечный автомат, где большая часть вашего кода в вашем асинхронном методе перемещается в MoveNext сгенерированного компилятором типа (структура в сборках Release),  и с этим MoveNext  завален переходами и метками, которые позволяют методу приостановить и резюмировать в пункты  await. К незавершенным задачам await подключено продолжение (обратный вызов), которое при окончательном завершении задачи вызывает метод MoveNext и переходит к месту, где функция была приостановлена. Для того чтобы локальные переменные могли поддерживать свое состояние через эти выходы и повторные входы метода, соответствующие «локальные объекты» переписываются компилятором в поля типа конечного автомата. И для того, чтобы этот конечный автомат как структура сохранялся в тех же самых приостановках, он должен быть перемещен в кучу.

Компилятор C # и среда выполнения .NET изо всех сил стараются не помещать этот конечный автомат в кучу. Многие вызовы асинхронных методов фактически завершаются синхронно, и компилятор и среда выполнения настраиваются на этот вариант использования.  Как указано, в релизном билде, конечный автомат, сгенерированный компилятором, является структурой, и когда вызывается асинхронный метод, конечный автомат начинает свою жизнь в стеке. Если асинхронный метод завершается без приостановки, конечный автомат успешно завершится, никогда не вызывая распределение. Однако, если асинхронный метод когда-либо нужно приостановить, конечный автомат должен быть каким-то образом собран.

В .NET Framework, момент Task или  ValueTask возвращение асинхронного метода (как общего, так и не универсального) приостанавливается впервые, происходит несколько выделений:

1. Структура конечного автомата копируется в кучу через стандартный бокс времени выполнения; каждый конечный автомат реализует IAsyncStateMachine  интерфейс, среда выполнения буквально приводит структуру к этому интерфейсу.

2. Среда выполнения фиксирует текущий ExecutionContext а затем выделяет объект (он называет это «бегун»), который он использует для хранения обоих конечных автоматов в штучной упаковке и ExecutionContext  (обратите внимание, что в .NET Framework, регистрация ExecutionContext  если это не значение по умолчанию, это также приводит к одному или нескольким выделениям).

3. Среда выполнения выделяет Action , который указывает на метод в этом объекте запуска, потому что шаблон ожидающего требует Action  которые будут переданы ожидающему методу {Unsafe}OnCompleted; при вызове Action будет использовать ExecutionContext для вызова метода MoveNext на конечном компьютере.

4. Среда выполнения выделяет объект Task, который будет завершен после завершения асинхронного метода и это возвращается из асинхронного метода к его синхронному вызывающему (если асинхронный метод набран для возврата ValueTask, структура ValueTask просто оборачивается вокруг объекта Task).

Это как минимум четыре средства, когда асинхронный метод в первый раз приостанавливается. Кроме того, каждый последующий раз асинхронный метод приостанавливается, если мы окажемся с нестандартным ExecutionContext  (например, он переносит состояние для AsyncLocal<T>),  среда выполнения перераспределяет это в запускающийся объект, затем перераспределяет действие, которое указывает на него (потому что делегаты неизменны), каждый раз выполняя минимум два дополнительных выделения, когда асинхронный метод приостанавливается после первого раза. Вот простое повторение этого в Visual Studio с правым окном, в котором отображаются распределения в соответствии с инструментом отслеживания распределения объектов .NET:

Это было значительно улучшено, в .NET Core, особенно с .NET Core 2.1. Когда асинхронный метод приостанавливается, выделяется Task. Но это не базовый тип Task или Task<TResult>. Структура конечного автомата хранится в строго типизированном поле для этого производного типа, устранение необходимости в отдельном распределении бокса. Этот тип также имеет поле для захваченного ExecutionContext (который является неизменным в .NET Core, это означает что захват никогда не выделяется), нам не нужен отдельный объект ранера. И у среды выполнения теперь есть специальные пути кода, которые поддерживают передачу этого типа AsyncStateMachineBox<TStateMachine> напрямую всем ожидающим, о которых среда выполнения знает, это означает, что пока асинхронный метод ожидает только TaskTask<TResult>ValueTask или ValueTask<TResult> (напрямую или через их аналоги ConfigureAwait), ему вообще не нужно выделять Action. Затем, поскольку у нас есть прямой доступ к полю ExecutionContext, последующие приостановки не требуют выделения нового участника (участники полностью отсутствуют), это также означает, что даже если нам нужно было распределить действие, нам не нужно его перераспределять. Это значит, тогда как в .NET Framework у нас есть как минимум четыре выделения для первой приостановки и часто по крайней мере два распределения для каждой последующей приостановки, в .NET Core у нас есть одно распределение для первого приостановления (наихудший случай два, если используются пользовательские ожидающие), и это все. Другие изменения, такие как переписывание инфраструктуры очередей ThreadPool, также значительно сократили распределение.

 

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

ValueTask

ValueTask<TResult> был введен в период .NET Core 1.0, чтобы помочь разработчикам избежать выделения ресурсов,  когда асинхронные методы завершаются синхронно. Это была относительно простая структура, представляющая различаемое объединение между TResult и Task<TResult>. При использовании в качестве типа результата асинхронного метода, если вызов асинхронного метода возвращается синхронно, независимо от значения результата TResult, метод требует нулевого распределения накладных расходов: конечный автомат не нужно перемещать в кучу, и нет необходимости в задании Task<TResult> для результата; значение результата просто сохраняется в поле TResult возвращенной ValueTask<TResult>. Однако, если асинхронный метод завершается асинхронно, среда выполнения возвращается к поведению так же, как и в случае с задачей Task<TResult>: он создает единственную задачу AsyncStateMachineBox<TStateMachine>, который затем возвращается в структуру ValueTask<TResult>.

В .NET Core 2.1 мы представили интерфейс IValueTaskSource<TResult>, наряду с неуниверсальными аналогами ValueTask и IValueTaskSource. Мы также сделали ValueTask<TResult> способным хранить не только TResult, но и Task<TResult>, но также IValueTaskSource<TResult> (то же самое для неуниверсального ValueTask, который, может хранить Task или IValueTaskSource). Этот продвинутый интерфейс позволяет предприимчивому разработчику написать свое собственное резервное хранилище для задачи,  и они могут делать это таким образом, чтобы повторно использовать этот объект хранилища резервных копий для нескольких не параллельных операций (намного больше информации об этом доступно в этом посте). Например, Socket обычно используется не более чем для одной операции приема и одна операция отправки за раз. Socket  был изменен для хранения повторно используемого / сбрасываемого IValueTaskSource<int> для каждого направления и каждой последующей операции чтения или записи что завершает и асинхронно раздает ValueTask<int>, поддерживаемый соответствующим общим экземпляром. Это означает, что в подавляющем большинстве случаев методы ReceiveAsync/SendAsync на основе ValueTask<int> в Socket  в конечном итоге не выделяются, независимо от того, выполняются они синхронно или асинхронно.  Несколько типов получили эту обработку, но только в тех случаях, где это будет действенно.

Таким образом, в .NET Core 2.1 были добавлены несколько реализаций в ключевых областях, таких как System.Net.SocketsSystem.Threading.Channels и System.IO.Pipelines, но не намного дальше. Впоследствии мы ввели тип ManualResetValueTaskSource<TResult>, чтобы упростить такие реализации, и в результате было добавлено больше реализаций этих интерфейсов в .NET Core 3.0, а также в .NET 5, хотя в основном внутри различных компонентов, таких как System.Net.Http.

Улучшения в .NET 5

В .NET 5 мы дальше экспериментируем с этой оптимизацией. С .NET 5 Preview 1, если до запуска вашего процесса для переменной среды DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS установлено значение true  или 1, среда выполнения будет использовать объекты конечного автомата, которые реализуют интерфейсы IValueTaskSource и IValueTaskSource<TResult>. Он будет объединять объекты, которые создает, для возвращенных экземпляров из асинхронных методов async ValueTask  или async ValueTask<TResult>. Итак, если, как и в предыдущем примере, вы повторно вызываете один и тот же метод и ожидаете результата, каждый раз, когда вы в конечном итоге получите ValueTask, который оборачивает один и тот же объект, просто сбрасывайте его каждый раз, чтобы позволить отслеживать другое выполнение. Магия.


Почему он не включен по умолчанию прямо сейчас? Две основные причины:

1. Объединение не является бесплатным. Разработчик может оптимизировать свой код различными способами. Один из них - просто улучшить код, чтобы больше не нуждаться в выделении; с точки зрения производительности это, как правило, очень низкий риск. Другой - повторно использовать уже доступный объект, например, добавив дополнительное поле к существующему объекту с аналогичным сроком службы; это все еще требует более подробного анализа производительности. Затем приходит объединение. Оно может быть очень полезным, когда создание объединяемой вещи очень ценно; хорошим примером этого является пул соединений HTTPS, где стоимость установления нового безопасного соединения, как правило, на несколько порядков дороже, чем доступ к нему даже в самой наивной структуре пулов данных. Более спорная форма объединения - это когда пул предназначен для дешевых объектов с целью избежать затрат на сборку мусора. Используя такой пул, разработчик делает ставку на то, что он может реализовать собственный распределитель (который действительно является пулом), который лучше, чем универсальный распределитель GC. Победить в сборной нетривиально. Но разработчик может это сделать, учитывая знания, которые они имеют в своем конкретном сценарии. Например, .NET GC очень хорошо умеет эффективно собирать недолговечные объекты, которые становятся коллекционными в поколении 0, и попытка объединения таких объектов может легко сделать программу более дорогой (даже если это выглядит хорошо на микробенчмарке, сфокусированном на измерении распределения). Но если вы знаете, что ваши объекты, вероятно, выживут в gen0, например, если они используются для представления потенциально асинхронных операций с большой задержкой, вполне возможно, что пользовательский пул может сбрить некоторые накладные расходы. Мы еще не сделали этот асинхронный пул async ValueTask по умолчанию, потому что, хотя он хорошо выглядит на микробенчмарках, мы не уверены, что это действительно значительное улучшение рабочих нагрузок в реальном мире.

2. ValueTasks имеют ограничения. Типы Task  и Task<TResult> были разработаны, чтобы быть очень надежными. Вы можете их кешировать. Вы можете ждать их любое количество. Они поддерживают несколько продолжений. Они потокобезопасны, с любым количеством потоков, способных одновременно регистрировать продолжения. И в дополнение к ожиданию и поддержке асинхронных уведомлений о завершении, они также поддерживают модель блокировки, при этом синхронные абоненты могут ожидать получения результата. Ничто из этого не относится к ValueTask и ValueTask<TResult>. Потому что они могут быть поддержаны сбрасываемыми экземплярами IValueTaskSource, вы не должны их кэшировать (то, что они переносят, может быть использовано повторно) и не ждать их несколько раз. Вы не должны пытаться зарегистрировать несколько продолжений (после первого завершения объект может попытаться сбросить себя для другой операции), будь то одновременно или нет. И вы не должны пытаться блокировать ожидание их завершения (реализации IValueTaskSource не должны предоставлять такую семантику). Пока вызывающие абоненты напрямую ожидают результата вызова метода, который возвращает  ValueTask или ValueTask<TResult>, все должно работать хорошо, но как только кто-то сходит с этого золотого пути, все может быстро измениться; это может означать получение исключений или коррупцию в процессе. Кроме того, эти сложности обычно проявляются только тогда, когда ValueTask или ValueTask<TResult> переносят реализацию IValueTaskSource; когда они переносят Task, вещи обычно «просто работают», так как ValueTask наследует надежность Task, и когда они оборачивают необработанное значение результата, ограничения технически вообще не применяются. И это означает, что, переключая асинхронные методы async ValueTask с поддержкой Task, вместо поддержки этих объединенных реализаций IValueTaskSource, мы могли бы выявлять скрытые ошибки в приложении разработчика, либо напрямую, либо через библиотеки, которые они используют. Предстоящий выпуск Roslyn Analyzers будет включать в себя анализатор, который должен помочь найти большинство нецелевых использований.

Призыв к действию

Если у вас есть приложение, которому, по вашему мнению, будет полезно это объединение, мы будем рады получить от вас сообщение. Скачивайте .NET 5 Preview 1. Попробуйте включить эту функцию. Что-нибудь сломается, в вашем коде, или в другой библиотеке, или в самом .NET. Или вы увидите значительные изменения в производительности, такие как пропускная способность или задержка, или рабочий набор, или что-то еще интересное. Обратите внимание, что изменение касается только асинхронных методов async ValueTask и async ValueTask<TResult>, поэтому, если у вас есть async Task или async Task<TResult>, вам также может потребоваться сначала изменить их, чтобы использовать их эквиваленты ValueTask.

Выпуск dotnet / runtime # 13633 отбражает виденье того, что мы должны делать с этой функцией для .NET 5, и Microsoft хочет услышать ваш фидбек; команда будет рада, если вы опубликуете какие-либо мысли или результаты.


Источник



Exception: Object reference not set to an instance of an object.
Posted on 17. January 2020

Получение и анализ дампов памяти

Опираясь на улучшения диагностики, представленные в .NET Core 3.1, мы представили новый инструмент для сбора дампов кучи из запущенного процесса .NET Core.

В предыдущем посте мы представили dotnet-dump, инструмент, позволяющий вам собирать и анализировать дампы процессов. С тех пор мы усердно работали над улучшением работы с дампами.

Два ключевых улучшения, которые мы внесли в dotnet-dump:

Нам больше не требуется sudo  для сбора дампов в Linux

dotnet dump analyze теперь поддерживается в Windows

GC дампы

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

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

Эти дампы GC полезны для нескольких сценариев, включая:

Сравнение количества объектов по типу в куче

Анализ корней объекта

Нахождение каких объектов имеет ссылку на какой тип

Другой статистический анализ об объектах в куче

dotnet-gcdump

 

В .NET Core 3.1 мы представляем новый инструмент, который позволяет вам захватывать вышеупомянутые дампы процессов для анализа в PerfView и Visual Studio.

Вы можете установить этот глобальный инструмент .NET, выполнив следующую команду:

dotnet tool install --global dotnet-gcdump

 

Установив dotnet gcdump, вы можете перехватить дамп GC, выполнив следующую команду:

dotnet gcdump collect -p <target-process-PID>

 

Примечание: Сбор gcdump запускает полную сборку мусора Gen 2 в целевом процессе и может изменить характеристики производительности вашего приложения. Продолжительность паузы GC, испытываемая приложением, пропорциональна размеру кучи GC; приложения с большими кучами будут испытывать более длительные паузы.

Полученный файл.gcdump можно проанализировать в Visual Studio и PerfView в Windows.

Анализ дампов GC в Visual Studio

Собранные дампы GC можно проанализировать, открыв файлы .gcdumpв Visual Studio. После открытия в Visual Studio вы попадаете на страницу отчета об анализе памяти.

В верхней панели отображается количество и размер типов в снимке, включая размер всех объектов, на которые ссылается тип (Inclusive Size).

В нижней панели дерево Paths to Root отображает объекты, которые ссылаются на тип, выбранный в верхней панели. В дереве ссылочных типов отображаются ссылки, содержащиеся в типе, выбранном в верхней панели.

В дополнение к отчету об анализе памяти только одного дампа GC, Visual Studio также позволяет сравнивать два дампа GC. Чтобы просмотреть подробную информацию о разнице между текущим снимком и предыдущим снимком, перейдите в раздел отчета Compare To и выберите другой дамп GC, который будет использоваться в качестве базовой линии.

Заключение

Спасибо за опробование новых инструментов диагностики в .NET Core 3.1. Пожалуйста, продолжайте оставлять свои отзывы, либо в комментариях, либо на GitHub Мы внимательно прислушиваемся к вашим отзывам и будем вносить изменения.

 

Источник



Exception: Object reference not set to an instance of an object.
Posted on 12. December 2019

Знакомство с System.Threading.Channels

Проблемы «производитель / потребитель» существуют везде, во всех аспектах нашей жизни. Последовательность приготовления заказа в заведениях быстрого питания: повар, нарезающий помидоры, которые передаются другому который складывает гамбургер, который предают работнику выдачи вашего заказа, который вы после с удовольствием съедаете. Почтовые водители доставляют почту по всем своим маршрутам, вы либо наблюдаете за прибывшим грузовиком и забираете свои посылки, либо же проверяете ящик придя с работы. Сотрудник авиакомпании выгружает чемоданы из грузового отсека реактивного лайнера, помещая их на конвейерную ленту, где они доставляются другому работнику, который переносит сумки в фургон и доставляет их на еще один конвейер, который привезет их к вам. Счастливая пара готовится разослать приглашения на свою свадьбу, причем один партнер закрывает конверты, передает другому который заклеивает и кладет в ящик.

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

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

Что такое Канал?

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

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

Для начала нам нужен наш тип, к которому добавим несколько простых методов:

public sealed class Channel<T>

{

    public void Write(T value);

    public ValueTask<T> ReadAsync(CancellationToken cancellationToken = default);

}

 

Метод Write позволяет нам его использовать для вывода данных в канал, и ReadAsync метод дает нам потреблять от него. Так как решили, что наш канал неограничен, ввод данных в него всегда завершается успешно и синхронно, так же, как запрос Add в List<T> поэтому сделали его не асинхронным и возвращающим пустоту. В противоположность, наш метод использования это ReadAsync который является асинхронным, потому что данные, которые хотим использовать, могут быть еще не доступны, и, таким образом, нам нужно будет дождаться их обновлений.  И пока начинаем разработку, не слишком обеспокоены производительностью, также не хотим иметь много ненужных дополнительных расходов. Так как ожидаем, что будем читать, данные которые уже доступны для использования, метод ­­­­­ ReadAsync возвращает ValueTask<T> а не к Task<T>, так что можем сделать это без выделения при синхронном завершении.

Теперь нам просто нужно реализовать эти два метода. Для начала добавим два поля к нашему типу: один, будет служить механизмом хранения, и вотрой для координации между производителями и потребителями:

private readonly ConcurrentQueue<T> _queue = new ConcurrentQueue<T>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0);

Используем ConcurrentQueue<T> для хранения данных, освобождая нас от необходимости делать собственную блокировку для защиты структуры данных буферизации, такую как ConcurrentQueue<T> она является потоко-безопасной для любого количества производителей/потребителей. И используем SempahoreSlim он помогает координировать между производителями и потребителей и уведомлять потребителей о поступлении дополнительных данных.

Наш метод Write прост. Ему нужно сохранить данные в очереди и увеличить счетчик SemaphoreSlim, “публикуя” его:

public void Write(T value)
{
    _queue.Enqueue(value); // store the data
    _semaphore.Release(); // notify any consumers that more data is available
}

 

Метод ReadAsync почти так же прост. Нужно подождать, пока данные будут доступны, а затем вынуть их.

public async ValueTask<T> ReadAsync(CancellationToken cancellationToken = default)
{
    await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); // wait
    bool gotOne = _queue.TryDequeue(out T item); // retrieve the data
    Debug.Assert(gotOne);
    return item;
}

 

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

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

Теперь, когда понимаем основы того, что предоставляет канал, можем перейти к рассмотрению реальных API-интерфейсов System.Threading.Channel.

 

Представляем System.Threading.Channels

Основные абстракции, представленные в библиотеке System.Threading.Channels, написаны так:

public abstract class ChannelWriter<T>
{
    public abstract bool TryWrite(T item);
    public virtual ValueTask WriteAsync(T item, CancellationToken cancellationToken = default);
    public abstract ValueTask<bool> WaitToWriteAsync(CancellationToken cancellationToken = default);
    public void Complete(Exception error);
    public virtual bool TryComplete(Exception error);
}

 

и читается:

public abstract class ChannelReader<T>
{
    public abstract bool TryRead(out T item);
    public virtual ValueTask<T> ReadAsync(CancellationToken cancellationToken = default)
    public abstract ValueTask<bool> WaitToReadAsync(CancellationToken cancellationToken = default);
    public virtual IAsyncEnumerable<T> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default);
    public virtual Task Completion { get; }
}

 

Только что завершил свою собственную разработку и внедрение простого канала, большая часть поверхности API должна быть знакома. ChannelWriter<T> обеспечивает метод TryWrite, который очень похож на наш метод записи; тем не менее, это абстрактно и метод проб, который возвращает Boolean учитывать тот факт, что некоторые реализации могут быть ограничены в количестве элементов они могут на нем храниться и если канал был заполнен так, что запись не могла завершиться синхронно, TryWrite потребуется вернуть false, чтобы указать, что запись была неудачной.  Тем не менее ChannelWriter<T> также обеспечивает метод WriteAsync; в таком случае, канал переполнен и запись должна ждать (часто упоминается как «обратное воздействие»), можно использовать WriteAsync, с производителем в ожидании результата WriteAsync его можно будет использовать, только когда пространство освободиться.

Конечно, бывают ситуации, когда код может не сразу создать значение; если полученные значения будут дорогим или если значение, представляет собой дорогой ресурс, (к примеру, это большой объект, который занял бы много памяти, или, может быть, он хранит кучу открытых файлов) в таких случаях, производитель работает быстрее, чем потребитель, производитель может захотеть отложить создание значения, пока не убедиться, что запись будет успешной. Для этого, есть соответствующий сценарий WaitToWriteAsync. Создатель может ожидать WaitToWriteAsync возвращение true, и только затем создатель выбирает значение TryWrite или WriteAsync на канал.

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

public async ValueTask WriteAsync(T item, CancellationToken cancellationToken)
{
    while (await WaitToWriteAsync(cancellationToken).ConfigureAwait(false))
        if (TryWrite(item))
            return;
 
    throw new ChannelCompletedException();
}

 

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

В этом примере также показано, почему WaitToWriteAsync возвращает ValueTask<bool> вместо ValueTask, а также ситуации за пределами полного буфера, в которых TryWrite может возвращать false. Каналы поддерживают концепцию завершения, когда производитель может сигнализировать потребителю, что больше не будет произведено никаких товаров, что позволяет потребителю прекратить попытки потреблять. Это делается с помощью методов Complete или TryComplete , ранее показанных на ChannelWriter<T> (Complete  реализован для вызова TryComplete  и throw, если он возвращает false). Но если один продюсер помечает канал как завершенный, другим продюсерам нужно знать, что они больше не могут писать в канал; в этом случае TryWrite возвращает false, WaitToWriteAsync также возвращает false, а WriteAsync генерирует исключение ChannelCompletedException.

Большинство членов ChannelReader<T>, вероятно, тоже говорят сами за себя.TryRead  попытается синхронно извлечь следующий элемент из канала. ReadAsync также извлекает следующий элемент из канала, но если элемент не может быть получен синхронно, он возвращает задачу. И WaitToReadAsync возвращает ValueTask<bool>, который служит уведомлением о доступе элемента. Как и в случае WriteAsync для ChannelWriter<T>, ReadAsync является виртуальным, с базовой реализацией, осуществляемой в терминах абстрактных TryRead  и WaitToReadAsync; это не точная реализация в базовом классе, но очень близка:

public async ValueTask<T> ReadAsync(CancellationToken cancellationToken)
{
    while (true)
    {
        if (!await WaitToReadAsync(cancellationToken).ConfigureAwait(false))
            throw new ChannelClosedException();
 
        if (TryRead(out T item))
            return item;
    }
}

 

Существует множество типичных шаблонов того, как использовать канал ChannelReader<T>. Если он представляет собой бесконечный поток значений, один из подходов состоит в том, чтобы просто сидеть в бесконечном цикле потребителя через ReadAsync:

while (true)
{
    T item = await channelReader.ReadAsync();
    Use(item);
}

 

Конечно, если поток значений не является бесконечным и канал будет в какой-то момент помечен как завершенный, после того как потребители очистят канал от всех своих данных, последующие попытки чтения из него будут отброшены. Напротив, TryRead  вернет false, как и WaitToReadAsync. Итак, более распространенная модель потребления - через вложенный цикл:

while (await channelReader.WaitToReadAsync())
    while (channelReader.TryRead(out T item))
        Use(item);

 

Вместо этого внутреннее «while» могло бы быть простым «if», но наличие тесного внутреннего цикла позволяет экономному разработчику избегать небольших дополнительных издержек WaitToReadAsync, так что TryRead будет успешно использовать этот элемент. Фактически, это точный шаблон, используемый методом ReadAllAsync. ReadAllAsync был представлен в .NET Core 3.0 и возвращает IAsyncEnumerable<T>. Это позволяет всем данным быть прочитанными из канала, используя знакомые языковые конструкции:

await foreach (T item in channelReader.ReadAllAsync())
    Use(item);

 

А базовая реализация виртуального метода использует точный шаблон вложенного цикла, показанный ранее с WaitToReadAsync и TryRead:

public virtual async IAsyncEnumerable<T> ReadAllAsync(
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    while (await WaitToReadAsync(cancellationToken).ConfigureAwait(false))
        while (TryRead(out T item))
            yield return item;
}

 

Последняя часть ChannelReader<T> это Completion. Возвращает Task, который будет выполнено после завершения чтения канала. Это означает, что писатель отметил, что канал завершен, и все данные были использованы.

Реализация встроенных каналов

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

Тип Channel<TWrite, TRead> предоставляет свойство Writer и свойство Reader, которое возвращает ChannelWriter<TWrite> и ChannelReader<TRead>  соответственно:

public abstract class Channel<TWrite, TRead>
{
    public ChannelReader<TRead> Reader { get;  }
    public ChannelWriter<TWrite> Writer { get; }
}

 

Этот базовый абстрактный класс доступен для нишевых случаев использования, когда канал сам может преобразовать записанные данные в другой тип для потребления, но в большинстве случаев использование TWrite и TRead совпадают, поэтому использование большинства происходит через производную Тип канала, который не более чем:

public abstract class Channel<T> : Channel<T, T> { }

 

Нетипичный Channel type обеспечивает фабрики для нескольких реализаций Channel<T>:

public static class Channel
{
    public static Channel<T> CreateUnbounded<T>();
    public static Channel<T> CreateUnbounded<T>(UnboundedChannelOptions options);
 
    public static Channel<T> CreateBounded<T>(int capacity);
    public static Channel<T> CreateBounded<T>(BoundedChannelOptions options);
}

 

Метод CreateUnbounded создает канал без ограничения на количество элементов, которые могут быть сохранены (конечно, в какой-то момент он может выйти за лимит памяти, так же, как с List<T> любые другие подборки), очень похоже на канальный тип, который реализовали в начале этого поста. TryWrite всегда возвращает true, и оба его WriteAsync и WaitToWriteAsync будут всегда завершаться синхронно.

Метод CreateBounded создает канал с явным ограничением. Как и в случае CreateUnbounded, TryWrite возвращает значение true, и WriteAsync и WaitToWriteAsync завершатся синхронно. Но как только канал заполняется, TryWrite возвращает false, и WriteAsync и WriteAsync завершаются асинхронно, выполняя свои возвращенные задачи только при наличии свободного места. (Само собой разумеется, что все эти API, которые принимают CancellationToken, также могут быть прерваны запросом отмены).

В CreateUnbounded, и CreateBounded есть перегрузки, которые принимает тип ChannelOption. Эта базовая опция предоставляет параметры, которые могут контролировать поведение любого канала. Например, он предоставляет свойства SingleWriter и SingleReader, которые позволяют создателю указывать ограничения, которые они готовы принять; разработчик устанавливает для SingleWriter значение true, чтобы указать, что один производитель будет одновременно обращаться к устройству записи, и аналогичным образом устанавливает для SingleReader значение true, чтобы указать, что не более одного потребителя будет одновременно обращаться к читателю. Это позволяет методам специализировать созданную реализацию, оптимизируя ее на основе предоставленных опций; например, если параметры, переданные CreateUnbounded, указывают на SingleReader как true, он возвращает реализацию, которая также позволяет избежать операций с блокировкой при чтении, что значительно снижает накладные расходы, связанные с потреблением из канала. Базовые ChannelOptions также предоставляют свойство AllowSynchronousContinuations. Как и в случае с SingleReader и SingleWriter, по умолчанию используется значение false, а для создателя значение true означает подписку на некоторые оптимизации, которые также имеют серьезные последствия для написания кода.  В частности, AllowSynchronousContinuations в некотором смысле позволяет производителю временно стать потребителем. Допустим, в канале нет данных, и потребитель приходит и вызывает ReadAsync. Ожидая задачу, возвращенную из ReadAsync, этот потребитель эффективно подключает обратный вызов, который будет вызываться при записи данных в канал. По умолчанию этот обратный вызов будет вызываться асинхронно, при этом производитель записывает данные в канал, а затем ставит в очередь вызов этого обратного вызова, что позволяет производителю одновременно идти своим путем, пока потребитель обрабатывается каким-то другим потоком. Однако в некоторых ситуациях для производительности может быть выгодно, чтобы этот производитель, записывающий данные, также сам обрабатывал обратный вызов, например, вместо того, чтобы TryWrite ставил в очередь вызов обратного вызова, он просто вызывает сам обратный вызов. Это может значительно сократить накладные расходы, но также требует глубокого понимания окружающей среды, так как, например, если вы удерживали блокировку при вызове TryWrite, а для параметра AllowSynchronousContinuations установлено значение true, вы можете в конечном итоге вызвать обратный вызов, удерживая блокировку, который (в зависимости от того, что пытался сделать обратный вызов) можете в конечном итоге наблюдать за некоторыми сломанными инвариантами, которые пытается поддерживать ваша блокировка.

BoundedChannelOptions передается слоям CreateBounded для дополнительных опций, связанных с ограничением. В дополнение к максимальной емкости, поддерживаемой каналом, он также предоставляет перечисление BoundedChannelFullMode, которое указывает, что записи поведения должны возникать при заполнении канала:

  public enum BoundedChannelFullMode
{
    Wait,
    DropNewest,
    DropOldest,
    DropWrite
}

 

TryWrite  на полном канале возвращает false, WriteAsync  вернет задачу, которая будет выполнена только тогда, когда освободится место, и запись может быть успешно завершена, и аналогично WaitToWriteAsync  будет завершена только тогда, когда пространство станет доступным. Вместо этого в трех других режимах запись всегда выполняется синхронно, удаляя элемент, если канал заполнен. DropOldest удалит «самый старый» элемент из очереди, что означает, что какой-либо элемент будет в дальнейшем удален потребителем. И наоборот, DropNewest удалит новый элемент, какой элемент был записан в канал в последний раз. И DropWrite удаляет элемент, который в данный момент пишется, то есть, например, TryWrite вернет true, но добавленный элемент будет немедленно удален.

Выполнение

С точки зрения API, представленные абстракции относительно просты, что является большой частью того, откуда исходит сила библиотеки. Простые абстракции и несколько конкретных реализаций, которые должны удовлетворять потребности разработчиков на 99,9%. Конечно, площадь поверхности библиотеки может указывать на простоту реализации. По правде говоря, в реализации есть приличная сложность, в основном сфокусированная на обеспечении высокой пропускной способности при одновременном использовании простых шаблонов потребления, легко используемых в потреблении кода. Реализация, например, делает все возможное, чтобы минимизировать распределение. Возможно, вы заметили, что многие методы возвращают ValueTask и ValueTask<T>, а не Task и Task<T>. Как увидели в нашем тривиальном примере реализации в начале этой статьи, мы можем использовать ValueTask<T>, чтобы избежать выделения при синхронном завершении методов, но реализация System.Threading.Channels также использует расширенные интерфейсы IValueTaskSource и IValueTaskSource<T>, чтобы избежать выделения ресурсов даже когда различные методы завершаются асинхронно и нужно возвращать задачи.

Рассмотрим этот тест:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Threading.Channels;
using System.Threading.Tasks;
 
[MemoryDiagnoser]
public class Program
{
    static void Main() => BenchmarkRunner.Run<Program>();
 
    private readonly Channel<int> s_channel = Channel.CreateUnbounded<int>();
 
    [Benchmark]
    public async Task WriteThenRead()
    {
        ChannelWriter<int> writer = s_channel.Writer;
        ChannelReader<int> reader = s_channel.Reader;
        for (int i = 0; i < 10_000_000; i++)
        {
            writer.TryWrite(i);
            await reader.ReadAsync();
        }
    }
}

 

Здесь мы просто тестируем пропускную способность и распределение памяти на неограниченном канале при записи элемента, а затем читаем этот элемент 10 миллионов раз, это означает, что элемент всегда будет доступен для чтения, и, таким образом, чтение всегда будет завершаться синхронно, что дает следующие результаты на моем компьютере (72 байта, показанные в столбце Allocated, предназначены для одной Задачи, возвращенной из WriteThenRead):

 

Method                                          Mean                            Error StdDev Gen 0    Gen 1    Gen 2    Allocated

WriteThenRead                           527.8 ms                    2.03 ms              1.90 ms                                                       72 B

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

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Threading.Channels;
using System.Threading.Tasks;
 
[MemoryDiagnoser]
public class Program
{
    static void Main() => BenchmarkRunner.Run<Program>();
 
    private readonly Channel<int> s_channel = Channel.CreateUnbounded<int>();
 
    [Benchmark]
    public async Task ReadThenWrite()
    {
        ChannelWriter<int> writer = s_channel.Writer;
        ChannelReader<int> reader = s_channel.Reader;
        for (int i = 0; i < 10_000_000; i++)
        {
            ValueTask<int> vt = reader.ReadAsync();
            writer.TryWrite(i);
            await vt;
        }
    }
}

 

который на моей машине за 10 миллионов операций записи и чтения дает такие результаты:

Method                                          Mean                            Error StdDev Gen 0    Gen 1    Gen 2    Allocated

ReadThenWrite                           881.2 ms                    4.60 ms              4.30 ms                                                       72 B

 

Итак, есть некоторые дополнительные издержки, когда каждое чтение завершается асинхронно, но даже здесь мы видим нулевое распределение для 10 миллионов асинхронно завершающих чтений (опять же, 72 байта, показанные в столбце Allocated, предназначены для Задачи, возвращаемой из ReadThenWrite)!

Комбинаторы

Обычно потребление каналов является простым, используя один из подходов, показанных ранее. Но, как и в случае с IEnumerables, можно также выполнять различные виды операций по каналам для достижения определенной цели. Например, допустим, я хочу дождаться поступления первого элемента от одного из двух предоставленных читателей; Я мог бы написать что-то вроде этого:

public static async ValueTask<ChannelReader<T>> WhenAny<T>(
    ChannelReader<T> reader1, ChannelReader<T> reader2)
{
    var cts = new CancellationTokenSource();
    Task<bool> t1 = reader1.WaitToReadAsync(cts.Token).AsTask();
    Task<bool> t2 = reader2.WaitToReadAsync(cts.Token).AsTask();
    Task<bool> completed = await Task.WhenAny(t1, t2);
    cts.Cancel();
    return completed == t1 ? reader1 : reader2;
}

 

Здесь мы просто запрашиваем WaitToReadAsync на обоих каналах и возвращаем считыватель в зависимости от того, что завершается первым. Одна из интересных вещей, которую стоит отметить в этом примере, это то, что в то время как ChannelReader<T> имеет много общего с IEnumerator<T>, этот пример не может быть хорошо реализован поверх IEnumerator<T> (или IAsyncEnumerator<T>).

I{Async}Enumerator<T> предоставляет метод MoveNext{Async}, который перемещает курсор вперед к следующему элементу, который затем открывается из Current. Если бы мы попытались реализовать такой WhenAny поверх IAsyncEnumerator<T>, нам нужно было бы вызвать MoveNextAsync для каждого. При этом мы потенциально могли бы перейти к следующему пункту. Если бы мы затем использовали этот метод в цикле, мы, скорее всего, в конечном итоге пропустили бы элементы из одного или обоих перечислителей, потому что мы потенциально могли бы расширить перечислитель, который мы не вернули из метода.

Отношение к остальной части .NET Core

System.Threading.Channels является частью общей платформы .NET Core, что означает, что приложение .NET Core может начать использовать его, не устанавливая ничего дополнительного. Он также доступен в виде отдельного пакета NuGet, хотя отдельная реализация не имеет всех оптимизаций, в значительной степени потому, что встроенная реализация может использовать преимущества дополнительной среды выполнения и поддержки библиотеки. NET Core.

Он также используется множеством других систем в .NET. Например, ASP.NET использует каналы как часть SignalR, а также в своей транспортировке Kestrel на основе Libuv. Каналы также используются будущей реализацией QUIC, которая в настоящее время разрабатывается для .NET 5.

Библиотека System.Threading.Channels также выглядит немного похожей на библиотеку System.Threading.Tasks.Dataflow, которая уже много лет доступна в .NET. В некотором смысле библиотека потоков данных является расширенным набором библиотеки каналов; в частности, тип BufferBlock<T> из библиотеки потока данных предоставляет большую часть той же функциональности. Однако библиотека потоков данных также ориентирована на другую модель программирования, в которой блоки связаны между собой так, что данные автоматически передаются от одного к другому. Он также включает расширенные функциональные возможности, которые поддерживают, например, форму двухфазной фиксации, с несколькими блоками, связанными с одними и теми же потребителями, и теми пользователями, которые позволяют извлекать механизмы из нескольких блоков без блокировки, которые необходимы для его включения, гораздо более сложны, и, в то же время, более мощные и более дорогие. Это очевидно, просто написав такой же тест для BufferBlock<T>, как мы делали ранее для каналов.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
 
[MemoryDiagnoser]
public class Program
{
    static void Main() => BenchmarkRunner.Run<Program>();
 
    private readonly Channel<int> _channel = Channel.CreateUnbounded<int>();
    private readonly BufferBlock<int> _bufferBlock = new BufferBlock<int>();
 
    [Benchmark]
    public async Task Channel_ReadThenWrite()
    {
        ChannelWriter<int> writer = _channel.Writer;
        ChannelReader<int> reader = _channel.Reader;
        for (int i = 0; i < 10_000_000; i++)
        {
            ValueTask<int> vt = reader.ReadAsync();
            writer.TryWrite(i);
            await vt;
        }
    }
 
    [Benchmark]
    public async Task BufferBlock_ReadThenWrite()
    {
        for (int i = 0; i < 10_000_000; i++)
        {
            Task<int> t = _bufferBlock.ReceiveAsync();
            _bufferBlock.Post(i);
            await t;
        }
    }
}

 

 

Method                                          Mean                            Error StdDev Gen 0    Gen 1    Gen 2    Allocated

Channel_ReadThenWrite       878.9 ms                    0.68 ms              0.60 ms                72 B                                 72 B

BufferBlock_ReadThenWrite                                  20,116.4 ms         192.82 ms           180.37 ms           1184000.0000    2000.0000                                                                                        7360000232 B

 

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

Что же дальше?

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

https://github.com/dotnet/runtime.

Источник

 



Exception: Object reference not set to an instance of an object.
Posted on 25. June 2019

Выпуск .NET Core 3.0 Preview 5

Совсем недавно вышел .NET Core 3.0 Preview 5. Он содержит новый Json сериализатор, поддержку публикации исполняемых файлов из одного файла, обновление для отката времени выполнения и изменения в BCL. Не забудьте ознакомиться с улучшениями в .NET Core 3.0 Preview 4.

Загрузите .NET Core 3.0 Preview 5 на Ваш Windows, macOS и Linux уже сейчас.

Также были обновлены ASP.NET Core и EF Core.

WPF и Windows Forms Обновления

Вы должны увидеть улучшение производительности при запуске  WPF и Windows Forms. Сборки WPF и Windows Forms теперь скомпилированы заранее, с использованием crossgen. Многие пользователи уже заметили улучшения при запуске между Preview 4 и Preview 5.

Было опубликовано больше WPF кода как часть .NET Core 3.0 Preview 4. Уже в Preview 7 будет окончательная WPF публикация.

Новый SqlClient

SqlClient - это поставщик данных, который Вы используете для доступа к SQL Server и базе данных SQL Azure либо через один из популярных .NET O/RM, например EF Core или Dapper, либо напрямую через API-интерфейсы ADO.NET.

В течение многих лет SqlClient поставлялся как часть сборки System.Data.dll в .NET Framework. Каждый раз, когда для использования новых функций SQL Server требовались изменения в SqlClient, приходилось ждать возможности, чтобы обновить .NET Framework на Windows. Раньше это работало нормально, поскольку новые функции SQL Server по-прежнему регулярно выпускались, разработка новых функций переходила на .NET Core и все вело к улучшению стабильности .NET Framework, и было больше смысла выводить разработку SqlClient из-под диапазона.

Введите Microsoft.Data.SqlClient, новую версию SqlClient, которую Вы можете добавить как NuGet пакет в .NET Framework и .NET Core (включая .NET Core 3.0) приложениях, которые сегодня запускаются в режиме предварительного просмотра.

Что нового в Microsoft.Data.SqlClient?

Отсутствие поддержки Always Encrypted в .NET Core было основной проблемой, которая была решена в этом предварительном просмотре.

Также ведется работа над двумя новыми функциями, доступными как для .NET Framework, так и для .NET Core:

  • Классификация данных
  • Поддержка UTF-8

В настоящее время планируется выпустить эти и другие улучшения в Microsoft.Data.SqlClient в те же сроки, что и в .NET Core 3.0.

Что значит для System.Data.SqlClient?

System.Data.SqlClient по-прежнему будет поддерживаться и получать важные обновления безопасности, поэтому нет необходимости перемещения, если Ваше приложение взаимодействует с ним. Но если Вы хотите воспользоваться новыми функциями, Вам следует рассмотреть обновление к Microsoft.Data.SqlClient. Процесс должен быть простым для многих приложений: просто установите пакет и обновите пространство SqlClient имен в Вашем коде. В некоторых других случаях потребуются изменения в конфигурации или обновленных O/RM версиях, которые зависят от нового SqlClient.

Следите за этим блог постом, чтобы узнать больше информации о новом SqlClient.

Публикация отдельных EXE-файлов

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

Вы можете включить этот параметр публикации, добавив свойство PublishSingleFile в файл проекта или добавив новый параметр в командной строке.

К примеру, чтобы создать отдельное EXE-приложение для 64-разрядной Windows версии:

dotnet publish -r win10-x64 /p:PublishSingleFile=true

Отдельные EXE-приложения должны соответствовать архитектуре. В результате необходимо указать идентификатор времени выполнения.

Смотрите Single file bundler для получения дополнительной информации.

Триммер сборки, преждевременная компиляция (через кроссген) и объединение отдельных файлов - новые функции в .NET Core 3.0, которые можно использовать вместе или по отдельности. Ожидайте услышать больше об этих трех особенностях в будущих preview-версиях.

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

JSON Сериализатор

Новый JSON сериализатор расположен поверх высокопроизводительных Utf8JsonReader и Utf8JsonWriter. Он десериализует объекты из JSON и сериализует объекты в JSON. Выделение памяти минимально и включает в себя поддержку чтения и записи JSON с Stream в асинхронном режиме.

Для начала используйте JsonSerializer класс в пространстве System.Text.Json.Serialization имен. Смотрите документацию с дополнительной информацией и примерами. Набор функций продолжает расширяться для будущих preview-версий.

Изменение дизайна Utf8JsonWriter

Основываясь на отзывах об удобстве использования и надежности, был внесен ряд изменений в Utf8JsonWriter дизайн, который был добавлен во второй preview-версии. Writer теперь является обычным классом, а не ref структурой, и реализует IDisposable. Это позволяет добавлять поддержку записи в потоки напрямую. Кроме того, был удален JsonWriterState, и теперь JsonWriterOptions необходимо передавать непосредственно в Utf8JsonWriter, который поддерживает свое собственное состояние. Чтобы помочь компенсировать распределение, Utf8JsonWriter имеет новый API сброса, который позволяет Вам сбросить его состояние и повторно использовать модуль записи. Также была добавлена встроенная реализация IBufferWriter<T> с именем ArrayBufferWriter<T>, которую можно использовать с Utf8JsonWriter. Вот фрагмент кода, который показывает изменения:

// New, built-in IBufferWriter that's backed by a grow-able array
var arrayBufferWriter = new ArrayBufferWriter();
 
// Utf8JsonWriter is now IDisposable
using (var writer = new Utf8JsonWriter(arrayBufferWriter, new JsonWriterOptions { Indented = true }))
{
 
   // Write some JSON using existing WriteX() APIs.
 
   writer.Flush(); // There is no isFinalBlock bool parameter anymore
}

Вы можете прочитать больше об изменении дизайна здесь.

Index и Range

В предыдущем предварительном просмотре платформа поддерживала Index и Range, предоставляя перегрузки общих операций, таких как индексаторы и методы, такие как Substring, которые принимали значения Index и Range. Основываясь на отзывах пользователей, все было упрощено и позволило компилятору вызывать существующие индексаторы. В документе «Index и Range изменения» содержится более подробная информация о том, как это работает, но основная идея заключается в том, что компилятор может вызывать индексатор на int основе, извлекая смещение из заданного значения Index. Это означает, что индексирование с использованием Index теперь будет работать для всех типов, которые предоставляют индексатор и имеют Count или Length свойство. Компилятор обычно не может использовать существующий индексатор для Range, потому что он возвращает только единичные значения. Однако теперь компилятор разрешает индексирование с использованием Range, когда тип предоставляет индексатор, который принимает Range, или Slice метод. Это позволяет Вам выполнять индексацию с использованием Range, а также работать с интерфейсами и типами, которые Вы не контролируете, предоставляя метод расширения.

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

string s = "0123456789";
char lastChar = s[^1]; // lastChar = '9'
string startFromIndex2 = s[2..]; // startFromIndex2 = "23456789"

Следующие String методы были удалены:

public String Substring(Index startIndex);
public String Substring(Range range);

Любой код, использующий эти String методы, должен быть обновлен для использования индексаторов

string substring = s[^10..]; // Replaces s.Substring(^10);
string substring = s[2..8];   // Replaces s.Substring(2..8);

Следующий Range метод ранее возвращал OffsetAndLength:

public Range.OffsetAndLength GetOffsetAndLength(int length);

Теперь он просто вернет строку:

public ValueTuple GetOffsetAndLength(int length);

Здесь продолжается компиляция и запуск, как и раньше:

(int offset, int length) = range.GetOffsetAndLength(20);

Новая японская эра (Reiwa)

1 мая 2019 года в Японии началась новая эра - так называемая Reiwa. Программное обеспечение, поддерживающее японские календари, например .NET Core, должно быть обновлено в соответствии с требованиями Reiwa. .NET Core и .NET Framework были обновлены и корректно обрабатывают японское форматирование и парсинг даных.

.NET полагается на операционную систему или другие обновления для правильной обработки Reiwa данных. Если Вы или Ваши клиенты используете Windows, загрузите последние Windows обновления. Если Вы используете macOS или Linux, загрузите и установите ICU 64.2 версию, которая поддерживает новую японскую эру.

В блог посте "Управление и переход к новой эре в японском календаре" содержит больше информации об изменениях, внесенных в .NET для поддержки новой японской эры.

Внутренние аппаратные API изменения

Avx2.ConvertToVector256* методы были изменены, чтобы возвращать подписанный тип, а не неподписанный. Это помещает их в строку с Sse41.ConvertToVector128* методами и соответствующими встроенными функциями. Например, Vector256<ushort> ConvertToVector256UInt16(Vector128<byte>) теперь является Vector256<short> ConvertToVector256Int16(Vector128<byte>).

Sse41/Avx.ConvertToVector128/256* методы были разделены на методы, которые принимают Vector128/256<T>, и методы, которые принимают T*. Например, ConvertToVector256Int16(Vector128<byte>) теперь поддерживает перегрузку ConvertToVector256Int16 (byte*). Это было сделано потому, что основная инструкция, которая принимает адрес, выполняет частичное векторное чтение (а не полное векторное или скалярное чтение). Это означало, что генерирация оптимального кодирования команд не всегда была возможной и пользователю приходилось выполнять чтение из памяти. Это разделение позволяет пользователю явно выбирать форму адресации инструкции при необходимости (например, когда у Вас еще нет Vector128<T>).

Записи перечисления FloatComparisonMode и Sse/Sse2.Compare методы были переименованы, чтобы уточнить, что операция упорядочена / неупорядочена. Они также были переупорядочены для обеспечения большего взаимодействия между SSE и AVX реализациями. Например, Sse.CompareEqualOrderedScalar теперь является Sse.CompareScalarOrderedEqual. Аналогично, для AVX версий Avx.CompareScalar (left, right, FloatComparisonMode.OrderedEqualNonSignalling) теперь Avx.CompareScalar(left, right, FloatComparisonMode.EqualOrderedNonSignalling).

Обновление политики среды выполнения .NET Core

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

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

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

  • LatestPatch — Переход к самой высокой версии. Это отключит второстепенный откат версии.
  • Minor — Переход к самой меньшей минорной версии, если запрашиваемая минорная версия отсутствует. Если запрашиваемая дополнительная версия присутствует, используется LatestPatch политика. Это политика по умолчанию.
  • Major — Переход к самой меньшей высокой версии и меньшей минорной версии, если запрашиваемая основная версия отсутствует. Если запрашиваемая основная версия присутствует, то используется дополнительная Minor политика.
  • LatestMinor — Переход к большей минорной версии, даже если запрошенная минорная версия присутствует.
  • LatestMajor — Переход к большей и большей минорной версии, даже если присутствует запрашиваемая главная версия.
  • Disable — Не откатывайтесь заранее. Привязывайтесь только к указанной версии. Эта политика не рекомендована для общего использования, так как она отключает возможность перехода к последним исправлениям. Рекомендуется только для тестирования.
Для получения дополнительной информации изучите блог посты Runtime Binding Behavior и dotnet/core-setup #5691.

Уменьшение докерных изображений .NET Core для Linux

Размер среды выполнения был сокращен примерно на 10 МБ, с помощью функции «частичного кроссгена».

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

Запустите несколько .NET Core приложений и соберите данные о том, какие методы вызываются. Этот процесс называется «обучение». Обучающие данные называются «IBC» и используются в качестве входных данных для перекрестного анализа, чтобы определить, какие методы компилировать.

Этот процесс полезен только в том случае, если Вы изучаете продукт с помощью представительных приложений. В противном случае это может повредить запуск. В настоящее время поставлена цель, сделать образы Docker контейнеров для Linux меньше. В результате, получится только .NET Core сборка для Linux, которая меньше по размеру и где был использован частичный кроссген. Это позволяет изучать .NET Core с меньшим набором приложений, потому что сценарий относительно узок. Это обучение было сосредоточено на .NET Core SDK (например, на запуске dotnet сборки и тестировании dotnet), приложениях ASP.NET Core и PowerShell.

В будущих выпусках будет расширено использование частичного кроссгена.

Docker Обновления

Теперь поддерживаются Alpine ARM64 образы. Также Linux образ по умолчанию переключен на Debian 10 / Buster. Debian 10 еще не выпущен. Есть вероятность, что он будет выпущен до .NET Core 3.0.

Добавилась поддержка Ubuntu 19.04 / Disco. Обычно не добавляется поддержка выпусков Ubuntu, не относящихся к LTS, но поддержка 19.04 была добавлена как часть процесса готовности к Ubuntu 20.04, следующей версии LTS. Поддержка для 19.10 будет добавлена при его выпуске.

О том, как использовать .NET Core и Docker вместе, Вы можете ознакомиться подробно здесь.

Обновления AssemblyLoadContext

AssemblyLoadContext продолжает улучшаться, чтобы в будущем простые модели плагинов работали без особых усилий (или кода) с Вашей стороны, и чтобы были возможны сложные модели плагинов. В Preview 5 была добавлена неявная загрузка типов и сборок через Type.GetType, когда вызывающая сторона не является приложением, например, сериализатором.

См. документ AssemblyLoadContext.CurrentContextualReflectionContext для получения дополнительной информации.

COM-вызываемые управляемые компоненты

Теперь Вы можете создавать управляемые COM-компоненты в Windows. Эта возможность очень важна для использования .NET Core с моделями COM надстроек, а также для обеспечения паритета с .NET Framework.

В .NET Framework использовался mscoree.dll в качестве COM-сервера. Вместе с .NET Core мы предоставляем встроенную библиотеку DLL запуска, которая добавляется в каталог bin компонента при сборке COM-компонента.

Изучите COM Server Demo, чтобы опробовать эту новую возможность.

Поддержка больших GC страниц

Большие страницы (также известные как «Огромные страницы на Linux») - это функция, при которой операционная система может устанавливать области памяти, превышающие собственный размер страницы (часто 4 КБ), чтобы повысить производительность приложения, запрашивающего эти большие страницы.

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

Теперь GC можно настроить с помощью GCLargePages в качестве опции для выбора размещения больших страниц на Windows. Использование больших страниц уменьшает количество TLB пропусков, поэтому потенциально может повысить производительность приложений. Это, однако, идет с некоторыми ограничениями.

В заключении

Обязательно попробуйте новый выпуск .NET Core 3.0. Пожалуйста, делитесь Вашими отзывами или комментариями на GitHub. Ваш вклад важен для будущего развития!

Взгляните на .NET Core 3.0 Preview 1, Preview 2, Preview 3 и Preview 4, если Вы пропустили эти выпуски. В этом посте Вы можете узучить полный набор новых возможностей, которые были добавлены к выпуску .NET Core 3.0.



Exception: Object reference not set to an instance of an object.
Posted on 31. May 2019

.NET Framework 4.8 [Part 2]

WCF - ServiceHealthBehavior

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

ServiceHealthBehavior - это поведение WCF сервиса, которое расширяет IServiceBehavior. При добавлении в коллекцию ServiceDescription.Behaviors будет включено следующее:

  • Возвращение состояния работоспособности сервиса с помощью ответов HTTP кода. В строке запроса можно указать код HTTP состояния для выполнения запроса проверки HTTP / GET работоспособности.
  • Публикация работоспособности сервиса: специфичные для сервиса детали, включая состояние сервиса и количество дросселей, отображаются с помощью HTTP / GET-запроса, используя “?health” строку запроса. Легкий доступ к отображаемой информации важны при устранении проблем WCF сервиса.

Конфигурация ServiceHealthBehavior:

Существует два способа предоставления конечной точки работоспособности и публикации информации о работоспособности WCF сервиса: с помощью кода или с помощью файла конфигурации.

  1. Включите конечную точку работоспособности, используя код
  2. Включите конечную точку работоспособности, используя конфигурацию

Верните статус работоспособности сервиса с помощью HTTP ответов кода:

Состояние работоспособности может запрашиваться параметрами запроса (OnServiceFailure, OnDispatcherFailure, OnListenerFailure, OnThrottlePercentExceeded). HTTP ответ кода (200 - 599) можно указать для каждого параметра запроса. Если HTTP ответ кода отсутствует для параметра запроса, по умолчанию используется 503 HTTP.

Параметры и примеры запросов:

1. OnServiceFailure: 

  • Пример: путем https://contoso:81/Service1?health&OnServiceFailure=450 запроса, код состояния HTTP-ответа 450 возвращается, когда ServiceHost.State больше, чем CommunicationState.Opened.

2. OnDispatcherFailure:

  • Пример: при запросе https://contoso:81/Service1?health&OnDispatcherFailure=455 код состояния HTTP 455 ответа возвращается, когда состояние любого из диспетчеров каналов больше, чем CommunicationState.Opened.

3. OnListenerFailure:

  • Пример: путем запроса https://contoso:81/Service1?health&OnListenerFailure=465 код состояния HTTP 465 ответа возвращается, когда состояние любого из прослушивателей канала больше, чем CommunicationState.Opened.

4. OnThrottlePercentExceeded: указывает процент {1 - 100}, который инициирует ответ, и его HTTP-код ответа {200 - 599}.

  • Пример: путем запроса https://contoso:81/Service1?health&OnThrottlePercentExceeded= 70:350,95:500 когда процент газа равен или превышает 95%, HTTP ответ кода 500; когда процент равен или больше 70% и меньше 95%, возвращается 350; в противном случае 200 возвращается. 

Публикация сервиса здоровья:

После включения конечной точки работоспособности состояние работоспособности сервиса может отображаться либо в html формате (указав строку запроса: https://contoso:81/Service1?health&Xml), либо в xml формате (указав строку запроса: https://contoso:81/Service1?health&Xml). https://contoso:81/Service1?health&NoContent возвращает пустую HTML-страницу.

Примечание:

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

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

WPF - экранные рассказчики больше не объявляют элементы с видимостью «Свернутый» или «Скрытый»

Элементы со свернутой или скрытой видимостью больше не объявляются программами чтения с экрана. Пользовательские интерфейсы, содержащие элементы с видимостью Свернутый или Скрытый, могут быть искажены программами чтения с экрана, если такие элементы объявлены пользователю. В .NET Framework 4.8 WPF больше не включает в себя элементы «Свернутый» или «Скрытый» в Control View структуры UIAutomation, поэтому программы чтения с экрана больше не могут объявлять эти элементы.

WPF - Свойство SelectionTextBrush для использования с Non-Adorner текстовым выделением

В .NET Framework 4.7.2 версии, в WPF добавилась возможность выделения TextBox и PasswordBox текста без использования adorner (см. здесь). Цвет переднего плана выделенного текста в этом сценарии был определен с помощью SystemColors.HighlightTextBrush.

В .NET Framework 4.8 будет добавлено новое свойство - SelectionTextBrush, которое позволит разработчикам выбирать конкретную кисть для выбранного текста при использовании выделения текста, основанного на non-adorner выделении текста.

Это свойство работает только для производных элементов управления TextBoxBase и PasswordBox в WPF приложениях с включенным выделением текста, не основанным на графическом элементе. Оно не работает на RichTextBox. Если выбор текста, не основанного на объявлении, не включен, это свойство игнорируется.

Чтобы использовать это свойство, просто добавьте его в Ваш XAML код и используйте соответствующую кисть или связывание.

В результате выбор текста будет выглядеть так:

Вы можете комбинировать использование SelectionBrush и SelectionTextBrush для создания любой подходящей цветовой комбинации фона и переднего плана.

WPF - Усовершенствования с высоким разрешением

В WPF добавлена поддержка Per-Monitor V2 DPI Awareness и смешанного DPI режима в .NET 4.8. Дополнительную информацию об этих Windows концепциях смотрите здесь.

В последнем руководстве разработчика по созданию WPF приложений говорится, что только приложения с чистым WPF кодом будут бесперебойно работать в WPF приложениях с высоким разрешением и что элементы управления Hosted HWND и Windows Forms будут поддерживаться не полностью.

В .NET 4.8 улучшена поддержка взаимодействия между HWND и Windows Forms в WPF приложениях с высоким разрешением на платформах, которые поддерживают Mixed-Mode DPI (Windows 10 версия 1803). Когда размещенные элементы управления HWND или Windows Forms создаются как масштабированные Mixed-Mode DPI окна (как описано в документации «Mixed-Mode DPI маштабирование и API-интерфейсы с DPI поддержкой» путем вызова SetThreadDpiHostingBehavior и SetThreadDpiAwarenessContext API), можно будет разместить такое содержимое в PerF Monitor V2 WPF приложении, и маштабировать их размер соответствующим образом.

Поддержка режима Per-Monitor V2 DPI осведомленности также позволяет размещать WPF элементы управления (т.е. помещать их в родительское окно) под нативным окном в приложении с высоким разрешением. Поддержка Per-Monitor V2 DPI Awareness будет доступна в Windows 10 версии 1607 (Anniversary Update). Windows добавляет поддержку дочерних HWND для получения уведомлений об изменении DPI, когда Per-Monitor V2 DPI Awareness режим включен через манифест приложения.

WPF использует эту поддержку, чтобы гарантировать, что элементы управления, размещенные в собственном окне, могут реагировать на DPI изменения и обновляться самостоятельно. Например, WPF элемент управления, размещенный в Windows Forms или Win32 приложении, которое отображается как Per Monitor V2, теперь сможет корректно реагировать на DPI изменения и сам обновляться.

Обратите внимание, что Windows поддерживает Mixed-Mode DPI масштабирование в Windows 10 версии 1803, тогда как Per-Monitor V2 поддерживается в версии 1607 и выше.

Чтобы опробовать эти функции, необходимо включить следующий манифест приложения и AppContext флажки:

1. Включите Per-Monitor DPI в Вашем приложении

  • Включите Per-Monitor V2 в Вашем app.manifest

2. Включите поддержку высокого разрешения в WPF

  • Нацельте .NET Framework 4.6.2 и выше

а также

3. Установите AppContext переключатель в Вашем app.config

Или же

Установите AAppContextSwitch Switch.System.Windows.DoNotUsePresentationDpiCapabilityTier2OrGreater=false в App.Config, чтобы включить поддержку Per-Monitor V2 и Mixed-Mode DPI, внедренную в .NET 4.8.

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

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

WPF - Поддержка свойства UIAutomation ControllerFor

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

Новый виртуальный метод был добавлен в AutomationPeer:

Чтобы предоставить значение для свойства ControllerFor, просто переопределите этот метод и верните список AutomationPeers для элементов управления, которыми манипулирует этот AutomationPeer:

WPF - Подсказки по доступу с клавиатуры

В настоящее время всплывающие подсказки отображаются только тогда, когда пользователь наводит курсор мыши на элемент управления. В .NET Framework 4.8, WPF добавляет функцию, позволяющую отображать всплывающие подсказки как на клавиатуре, так и с помощью сочетания клавиш.

Чтобы включить эту функцию, приложению необходимо настроить таргетинг на .NET Framework 4.8 или зарегистрироваться через AppContext переключатель «Switch.UseLegacyAccessibilityFeatures.3» и «Switch.UseLegacyToolTipDisplay».

Пример App.config файла:

 

 
 
      
         
     
   
     
   

 

После включения все элементы управления, содержащие подсказку, начнут отображать ее, как только элемент управления получит фокус клавиатуры. Подсказка может быть отклонена с течением времени или при изменении фокуса клавиатуры. Пользователи также могут отклонить всплывающую подсказку вручную с помощью сочетания клавиш Ctrl + Shift + F10. Как только всплывающая подсказка будет отклонена, ее можно снова отобразить с помощью того же сочетания клавиш.

Примечание. Подсказки RibbonTool на элементах управления ленты не отображаются на фокусе клавиатуры - они отображаются только через сочетание клавиш.

WPF - Добавлена поддержка свойств SizeOfSet и PositionInSet UIAutomation

Windows 10 представила новые свойства UIAutomation SizeOfSet и PositionInSet, которые используются приложениями для описания количества элементов в наборе. Клиентские приложения UIAutomation, такие как программы чтения с экрана, могут затем запросить приложение для этих свойств и объявить точное представление пользовательского интерфейса приложения.

Эта функция добавляет поддержку WPF приложений для предоставления этих двух свойств UIAutomation. Это может быть выполнено двумя способами:

1. DependencyProperties

Новые DependencyProperties SizeOfSet и PositionInSet были добавлены в пространство имен System.Windows.Automation.AutomationProperties. Разработчик может установить эти значения через XAML:

 

 
 

 

2. Виртуальные методы AutomationPeer

Виртуальные методы GetSizeOfSetCore и GetPositionInSetCore также были добавлены в AutomationPeer класс. Разработчик может предоставить значения для SizeOfSet и PositionInSet, переопределив эти методы:

 

public class MyButtonAutomationPeer : ButtonAutomationPeer 
    { 
        protected override int GetSizeOfSetCore() 
        { 
            // Call into your own logic to provide a value for SizeOfSet 
            return CalculateSizeOfSet(); 
        } 
         protected override int GetPositionInSetCore() 
        { 
            // Call into your own logic to provide a value for PositionInSet 
            return CalculatePositionInSet(); 
        } 
    }

 

Автоматические значения

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

Автоматические значения предоставляются только в том случае, если разработчик нацелен на .NET Framework 4.8 или установил AppContext переключатель «Switch.UseLegacyAccessibilityFeatures.3» - например, через App.config файл:

 
 
      
         
     
   
     
  
В заключение

Попробуйте эти улучшения в .NET Framework 4.8 и поделитесь Вашими отзывами в комментариях здесь или на GitHub.

Часть 1.

Источник




Exception: Object reference not set to an instance of an object.
Posted on 20. May 2019

ML.NET 1.0

Уже сегодня, выходит новый ML.NET 1.0. ML.NET - это кросс-платформенная система машинного обучения с открытым исходным кодом для использования возможностей машинного обучения (ML) в .NET приложениях.

https://github.com/dotnet/machinelearning 

Начните работу @ http://dot.net/ml 

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

Изначально ML.NET разрабатывался в рамках Microsoft Research и превратился в важную среду, используемую многими Microsoft продуктами, такими как Windows Defender, Microsoft Office (Проектирование идей для PowerPoint презентаций, Построение диаграмм в Microsoft Excel), Azure Machine Learning, Ключевые факторы влияния PowerBI и многие другие!

После первого выпуска ML.NET, он стал использоваться многими организациями, такими как SigParser (обнаружение спама в электронной почте), William Mullens (классификация юридических вопросов) и Evolution Software (определение уровня влажности лесных орехов). Вы можете ознакомиться с этими и многими другими организациями, которые используют ML.NET, на витрине ML.NET клиентов. Эти пользователи говорят нам, что простота использования ML.NET, возможность повторно использовать .NET навыки и полное сохранение технического стека в .NET являются основными причинами использования ML.NET.

Наряду с выпуском ML.NET 1.0 также были добавлены новые функции preview-версии, такие как автоматизированное машинное обучение (AutoML) и новые инструменты, такие как ML.NET CLI и ML.NET Model Builder, что позволит Вам добавлять модели машинного обучения в Ваши приложения в один клик!

Остальная часть этого поста посвящена новому функционалу.

Основные компоненты ML.NET

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

1. Представление данных
  • Фундаментальные типы данных конвейера ML данных, такие как IDataView - основной тип конвейера данных
  • Читалки для поддержки чтения данных из текстовых файлов с разделителями или IEnumerable объектов
2. Поддержка задач машинного обучения:
  • Бинарная классификация
  • Мультиклассовая классификация
  • Регрессия
  • Ранжирование
  • Обнаружение аномалий
  • Кластеризация
  • Рекомендация (preview)
3. Преобразование данных и индивидуализация
  • Текст
  • Категории
  • Выбор функций
  • Нормализация и обработка пропущенных значений
  • Подделка образов
  • Временные циклы (preview)
  • Поддержка интеграции ONNX и TensorFlow моделей (preview-версия)
4. Другое
  • Модель понимания и объяснимости
  • Пользовательские преобразования
  • Схема операций
  • Поддержка манипулирования наборами данных и перекрестной проверки
Automated Machine Learning (Preview-версия)

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

Automated Machine Learning упрощает Ваше начало в работе с ML, автоматически выясняя, как преобразовать входные данные, и выбирая для Вас наилучший ML алгоритм с правильными настройками, позволяющими легко создавать лучшие в своем классе пользовательские ML модели.

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

Вы можете попробовать AutoML в ML.NET в трех форм-факторах, используя ML.NET Model Builder, ML.NET CLI или прямо через API AutoML (примеры смотрите здесь).

Для пользователей, которые не знакомы с машинным обучением, рекомендуется начать с ML.NET Model Builder в Visual Studio и ML.NET CLI на любой поддерживаемой платформе. AutoML API также очень удобен для сценариев, в которых можно создавать модели на лету.

Model Builder (Preview-версия)

Чтобы упростить .NET разработчикам путь по созданию ML моделей, был выпущен ML.NET Model Builder. С помощью ML.NET Model Builder внедрение машинного обучения в Ваши приложения бует доступно в один клик!

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

Узнайте больше о ML.NET Model Builder

На данный момент, Model Builder находится в режиме предварительного просмотра, и Ваши отзывы и замечания будут очень полезными для его будущего развития!

ML.NET CLI (Preview-версия)

ML.NET CLI (интерфейс командной строки) - это еще один новый инструмент, который был недавно выпущен!

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

CLI в дополнение к созданию лучшей модели также позволяет пользователям генерировать модели обучения и код модели потребления для наиболее эффективной модели.

ML.NET CLI - это кросс-платформенная система, которая представляет собой простое дополнение к .NET CLI. Расширение Model Builder Visual Studio также использует ML.NET CLI для предоставления возможностей построителя моделей.

Вы можете установить ML.NET CLI, используя эту команду.

 

dotnet tool install -g mlnet

 

На следующем рисунке показан интерфейс командной строки ML.NET CLI, создающий набор данных для анализа настроений.

 

Узнайте больше о ML.NET CLI

На данный момент ML.NET CLI тоже находится в режиме предварительного просмотра, и Ваш фидбэк будет очень полезным для его дальнейшего развития!

Начните уже сейчас!

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

 

//Step 1. Create a ML Context
var ctx = new MLContext();
 
//Step 2. Read in the input data for model training
IDataView dataReader = ctx.Data
    .LoadFromTextFile(dataPath, hasHeader: true);
 
//Step 3. Build your estimator
IEstimator est = ctx.Transforms.Text
    .FeaturizeText("Features", nameof(SentimentIssue.Text))
    .Append(ctx.BinaryClassification.Trainers
        .LbfgsLogisticRegression("Label", "Features"));
 
//Step 4. Train your Model
ITransformer trainedModel = est.Fit(dataReader);
 
//Step 5. Make predictions using your model
var predictionEngine = ctx.Model
    .CreatePredictionEngine(trainedModel);
 
var sampleStatement = new MyInput { Text = "This is a horrible movie" };
 
var prediction = predictionEngine.Predict(sampleStatement);

 

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

Что будет дальше в ML.NET?

Не смотря на то, что недавно был выпущен ML.NET 1.0, команда продолжает усердно работать, чтобы как можно скорее добавить функции, описанные в этом блог посте, в следующий выпуск.

  • AutoML для дополнительных ML сценариев
  • Улучшенная поддержка сценариев глубокого обучения
  • Поддержка других источников, таких как SQL Server, CosmosDB, хранилище Azure Blob и других.
  • Масштабирование в Azure для модели обучения и потребления
  • Поддержка дополнительных сценариев и ML функций при использовании Model Builder и CLI
  • Встроенная интеграция для машинного обучения в масштабе с .NET для Apache Spark и ML.NET
  • Новые ML типы в .NET, например DataFrame
Вклад участников

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


Если Вы еще не начали, обязательно попробуйте ML.NET!

Ваше мнение очень важно для ML.NET улучшения и вскоре .NET будет лучшей платформой для машинного обучения.




Exception: Object reference not set to an instance of an object.
Posted on 10. May 2019

Улучшенная диагностика в .NET Core 3.0

В .NET Core 3.0 добавлен набор инструментов, которые используют новые функции в .NET среде, что значительно улучшает диагностику и производительность.

Эти функции помогут Вам ответить на некоторые распространенные вопросы диагностики:

  1. Все ли в порядке с моим приложением?
  2. Почему возникает аномальное поведение в моем приложении?
  3. Почему мое приложение падает?

Все ли в порядке с моим приложением?

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

Метрики

Метрики - это данные измерений за определенные промежутки времени. Данные метрик (или временных рядов) позволят Вам наблюдать за состоянием Вашей системы самым лучшим образом. В отличие от .NET Framework на  Windows, .NET Core не отслеживает производительность. Поэтому был добавлен новый способ генерирования метрик в .NET Core с помощью EventCounter API.

EventCounters намного лучше по сравнению с Windows счетчиками производительности, поскольку теперь они используются на всех ОС, где поддерживается .NET Core. Кроме того, в отличие от счетчиков производительности, они также могут использоваться в средах с низким уровнем привилегий (например, при xcopy развертывании). К сожалению, отсутствие такого инструмента, как Performance Monitor (perfmon), затрудняет использование этих метрик в режиме реального времени.

dotnet-counters

В .NET Core 3.0 Preview 5 есть новый инструмент командной строки для наблюдения за метриками, генерируемыми .NET Core Applications в режиме реального времени.

Вы можете установить этот .NET инструмент, выполнив следующую команду.

dotnet tool install --global dotnet-counters --version 1.0.3-preview5.19251.2

 

Для получения подробных инструкций о том, как использовать этот инструмент, перейдите на dotnet-counters readme. Для ознакомления с известными ограничениями dotnet-counters, перейдите на GitHub.

Почему возникает аномальное поведение в моем приложении?

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

Трассировка

Трассировки - это неизменные записи дискретных событий с отметкой времени. Трассировки содержат локальный контекст, который позволяет лучше определить сбой системы. Обычно .NET Framework (и такие фреймворки, как ASP.NET) генерировали диагностические трассировки о своих внутренних компонентах с помощью Event Tracing для Windows (ETW). В .NET Core эти трассировки были прописаны в ETW для Windows и LTTng для Linux.

dotnet-trace

В .NET Core 3.0 Preview 5 каждое .NET Core приложение открывает двойной канал с именем EventPipe (Unix сокет домен именованный pipe на Windows), через который можно отправлять события. На данный момент ведутся работы над протоколом контроллера, dotnet-trace реализует preview-версию этого протокола.

Вы можете установить этот .NET инструмент, выполнив следующую команду

dotnet tool install --global dotnet-trace--version 1.0.3-preview5.19251.2

 

В приведенном выше примере запускается dotnet trace с профилем по умолчанию, который включает аналитику событий ЦПУ и события .NET среды. 

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

В результате запуска dotnet trace Вы получаете .netperf файл. Этот файл содержит события времени выполнения и выборки ЦПУ стеков, которые можно визуализировать в perfview. Следующее обновление Visual Studio (16.1) также добавит поддержку для визуализации этих трассировок.

Если при записи трассировки Вы работаете на X или Linux, Вы можете преобразовать эти .netperf файлы в .speedscope.json файлы, которые можно визуализировать с помощью Speedscope.app.

Вы можете преобразовать уже существующую трассировку, выполнив следующую команду

dotnet trace convert <input-netperf-file>

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

Для получения подробных инструкций о том, как использовать этот инструмент, перейдите на dotnet-trace readme. Для ознакомления с известными ограничениями dotnet-trace, перейдите на GitHub.

Почему мое приложение падает?

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

Dump анализ

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

Обычно Вы полагались на операционную систему для получения dump анализа при сбое приложения (например, Windows Error Reporting) или использовали инструмент, такой как procdump, для доступа к dump при выполнении определенных критериев запуска.

До сих пор проблема с захватом dump анализа с помощью .NET на Linux заключалась в том, что захват дампов с помощью gcore или debugger приводил к очень большим сложностям, поскольку существующие инструменты не знали, какие страницы виртуальной памяти обрезать в .NET Core процессе.

Кроме того, было сложно проанализировать эти dump даже после того, как Вы их собрали, поскольку требовалось использовать debugger и настроить его для sos загрузки, debugger расширения для .NET.

dotnet-dump

В 3.0.0-preview5 добавлен новый инструмент, который собирает и анализирует dump процессы как на Windows, так и на Linux. 

dotnet-dump все еще находится в активной разработке, и в таблице ниже показано, какие функции и на каких ОС поддерживаются на данный момент.

  Windows OS X Linux
Collect
✅
❌
✅
Analyze
❌
❌
✅


Вы можете установить этот .NET инструмент, выполнив следующую команду

dotnet tool install --global dotnet-dump --version 1.0.3-preview5.19251.2

После того, как Вы установили dotnet dump, Вы можете записать dump процесс, выполнив следующую команду

sudo $HOME/.dotnet/tools/dotnet-dump collect -p <pid>

На Linux полученный dump можно проанализировать, загрузив его с помощью следующей команды

dotnet dump analyze <dump-name>

 

Для получения подробных инструкций о том, как использовать этот инструмент, перейдите на dotnet-dump readme. Для ознакомления с известными ограничениями dotnet-dump, перейдите на GitHub.

В заключение

 

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

 

Источник



Exception: Object reference not set to an instance of an object.
Posted on 12. March 2019

Как переносить настольные приложения на .NET Core 3.0

В этой статье будет рассмотрен перенос настольного приложения с .NET Framework на .NET Core. В качестве примера возьмем WinForms приложение. Шаги для WPF приложения схожи, различия рассмотрим по ходу. Также увидим как использовать WinForms designer в Visual Studio, хотя он находится в стадии разработки и еще не доступен для .NET Core проектов.

О примере

Для этого поста будет использовано игровое Memory-style board приложение. Оно состоит из WinForms UI (MatchingGame.exe) и библиотеки классов с игровой логикой (MatchingGame.Logic.dll), которые предназначены для .NET Framework 4.5. Здесь можно скачать образец. Итак, рассмотрим перенос проекта приложения на .NET Core 3.0, а также библиотеки классов на .NET Standard 2.0. Использование .NET Standard вместо .NET Core позволяет повторно использовать игровую логику для размещения приложения на других платформах, таких как iOS, Android или в вебе.

 

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

Пошаговый процесс

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

Прежде чем переносить приложение на .NET Core 3.0, подготовите следующее:

  1. Установите .NET Core 3 и Visual Studio 2019 Preview версию (Visual Studio 2017 поддерживает версии только до .NET Core 2.2).
  2. Начните с рабочего проекта. Убедитесь, что проект беспроблемно открывается, собирается и запускается.
  3. Обновите NuGet пакеты. Всегда рекомендуется использовать последние версии NuGet пакетов перед любым переносом. Если Ваше приложение ссылается на какие-либо NuGet пакеты, обновите их до последней версии. Убедитесь, что Ваше приложение успешно собрано. Если NuGet выдает ошибки, понизьте версию и найдите последнюю, которая не нарушает Ваш код.
  4. Запустите .NET Portability Analyzer, чтобы определить, существуют ли какие-либо API, от которых зависит Ваше приложение, и которые отсутствуют в .NET Core. Если таковые имеются, Вам необходимо провести рефакторинг своего кода, чтобы избежать зависимостей от API, не поддерживаемых в .NET Core. Иногда можно найти альтернативный API, который обеспечивает необходимую функциональность.
  5. Замените packages.config на PackageReference. Если используются NuGet пакеты, то нужно добавить те же NuGet пакеты в новый .NET Core проект. .NET Core проекты поддерживают только PackageReference для добавления NuGet пакетов. Чтобы переместить ссылки NuGet из packages.config в файл проекта, в обозревателе решений необходимо щелкнуть правой кнопкой на packages.config -> Migrate packages.config в PackageReference… Более детально об этом типе переноса можно ознакомиться по ссылке Migrate from packages.config to PackageReference.

 

Портирование основного проекта

Создание нового проекта
  • Создайте новое приложение того же типа (Console, WinForms, WPF, Class Library), что и приложение, которое нужно перенести, для .NET Core 3. На данный момент были сделаны демонстрационные Visual Studio шаблоны для десктопных проектов, которые находились в стадии разработки, поэтому использовалась консоль.
dotnet new winforms -o \MatchingGame.Core\
  • Скопируйте в файле проекта все внешние ссылки из старого проекта, например:
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
  • Начните сборку. На этом этапе, если пакеты, на которые Вы ссылаетесь, поддерживают только .NET Framework, Вы получите NuGet предупреждение. Если Вы не обновились к последней версии NuGet пакета на шаге 3, попробуйте определить, доступна ли последняя версия, поддерживающая .NET Core (.NET Standard), и повторите обновление. Если более новой версии нет, то .NET Framework пакеты все еще можно использовать, но можно получить ошибки времени выполнения, если эти пакеты имеют зависимости от API, не поддерживаемых в .NET Core. В таком случае рекомендуется сообщить автору NuGet пакета, что Вас заинтересовало бы обновление пакета до .NET Standard. Сделать это можно через контактную форму в NuGet галерее.
Быстрый способ (заменить существующий файл проекта)

Итак, быстрый способ переноса. Убедитесь, что у Вас есть копия текущего .csproj файла, возможно, Вам придется использовать его в будущем. Замените текущий .csproj файл .csproj файлом из проекта, созданного ранее, и добавьте вверху <PropertyGroup> следующее:

<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
Соберите приложение. Если ошибок не возникло, то перенос проекта на .NET Core 3 прошел успешно. Для перемещения зависимого (UI) проекта рекомендуется ознакомиться с разделом «Перенос пользовательского интерфейса»), чтобы узнать, как использовать Designer, ознакомьтесь с разделом «Использование WinForms Designer для проектов .NET Core».

Медленный путь (управляемый перенос)

Если система выдает ошибки, то нужно внести дополнительные корректировки. Здесь предоставлена информация о внесении изменений в код с возможными исправлениями для каждого вопроса. Шаги ниже также помогут лучше понять процесс переноса, поэтому, если быстрый способ помог, но Вам интересно узнать «почему и как», продолжайте читать.
  • Перейдите в.csproj файл в SDK-style. Чтобы переместить приложение в .NET Core, сначала нужно изменить файл проекта в SDK формате, поскольку старый формат не поддерживает .NET Core. Кроме того, SDK-style формат намного проще и с ним легче работать. Убедитесь, что копия текущего .csproj файла создана. Замените содержимое .csproj файла следующим. Для WinForms приложения:
  
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>
</Project></pre>
Для WPF приложения:

  
    <Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>
</Project>
Обратите внимание, что значение <GenerateAssemblyInfo> было установлено как false. В проектах нового стиля AssemblyInfo.cs генерируется автоматически по умолчанию. Так что, если проект уже содержит файл AssemblyInfo.cs файл (а он содержит), то необходимо отключить авто-генерацию или удалить файл.

Теперь скопируйте и вставьте все ссылки из старой версии .csproj файла в новую. Например:

Ссылка на NuGet пакет
 
<PackageReference Include="Microsoft.Windows.Compatibility" Version="2.0.1" />
Ссылка на проект
 
<ProjectReference Include="..\MatchingGame.Core\MatchingGame.Core.csproj" />

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

Существует также сторонний инструмент CsprojToVs2017, который может выполнить преобразование. Но после его использования все равно может понадобиться удалить некоторые ссылки вручную, например:
<Reference Include="System.Data.DataSetExtensions"></Reference>
<Reference Include="Microsoft.CSharp"></Reference>
<Reference Include="System.Net.Http"></Reference>

  • Перейдите от .NET Framework к .NET Standard или .NET Core. После успешного преобразования библиотеки в SDK формат, ее можно перенастроить. В данном случае, необходимо, чтобы библиотека классов предназначалась для .NET Standard вместо .NET Core. Таким образом, она будет доступна из любой .NET реализации, если нужно отправить игру на другие платформы (например, iOS, Android или Web Assembly). Для этого замените данную строку
<TargetFramework>net472</TargetFramework>
на следующую

<TargetFramework>netstandard2.0</TargetFramework>
Создайте Ваше приложение. Если Вы используете API, которые не включены в .NET Standard, то могут возникнуть ошибки. Если ошибок нет, то следующие два шага можно пропустить.

  • Добавьте Windows Compatibility Pack для совместимости, если необходимо.Некоторые API, которые не включены в .NET Standard, доступны в Windows пакете совместимости. Если на предыдущем шаге были получены ошибки, то можно проверить, поможет ли Windows пакет совместимости. В рассматриваемом приложении была получена следующая ошибка «Имя реестра не существует в текущем контексте», поэтому в проект был добавлен Microsoft.Windows.Compatibility NuGet пакет. После установки ошибка исчезла.
  • Установите API Analyzer. API Analyzer, доступный в виде NuGet пакета Microsoft.DotNet.Analyzers.Compatibility, предупредит об использовании устаревших API или API, которые не поддерживаются на всех платформах (Windows, Linux, macOS). Если был добавлен пакет совместимости, то рекомендуется добавить и API Analyzer для отслеживания случаев использования API, которые не будут работать на всех платформах. На этом рассмотрение процесса переноса библиотеки классов на .NET Standard заканчивается. Если у Вас есть несколько проектов, которые ссылаются друг на друга, переносите их «снизу вверх», начиная с проекта, который не зависит от других. В данном примере также есть WinForms MatchingGame.exe проект, поэтому теперь необходимо выполнить аналогичные шаги для его переноса в .NET Core.
Перенос пользовательского интерфейса
  • Добавьте .NET Core UI проект. Внедрите в решение новый .NET Core 3.0 UI проект. На данный момент Visual Studio шаблоны для настольных приложений находятся в стадии разработки, поэтому можно просто использовать dotnet CLI.
dotnet new winforms -o \MatchingGame.Core\
Для WPF проектов можно использовать следующее:

dotnet new wpf -o \MatchingGame.Core\
После того, как был создан новый WinForms .NET Core проект, его необходимо добавить в Ваше решение.
  • Свяжите проекты. Сначала удалите все файлы из нового проекта (прямо сейчас он содержит общий Hello World код). Затем свяжите все файлы из существующего .NET Framework UI проекта с .NET Core 3.0 UI проектом, добавив в .csprojfile файл следующую команду.
    
<ItemGroup>
    <Compile Include="..\\**\*.cs" />
    <EmbeddedResource Include="..\\**\*.resx" />
</ItemGroup>
Если у Вас WPF приложение, также необходимо включить .xaml файлы:

  
<ItemGroup>
  <ApplicationDefinition Include="..\WpfApp1\App.xaml" Link="App.xaml">
    <Generator>MSBuild:Compile</Generator>
</ApplicationDefinition>
<Compile Include="..\WpfApp1\App.xaml.cs" Link="App.xaml.cs"></Compile>
</ItemGroup>

<ItemGroup>
  <Page Include="..\WpfApp1\MainWindow.xaml" Link="MainWindow.xaml">
    <Generator>MSBuild:Compile</Generator>
  </Page>
  <Compile Include="..\WpfApp1\MainWindow.xaml.cs" Link="MainWindow.xaml.cs" />
</ItemGroup>
  • Совместите пространство имен по умолчанию и имя сборки. Поскольку Вы ссылаетесь на файлы, созданные дизайнером (например, Resources.Designer.cs), то нужно убедиться, что версия Вашего .NET Core приложения использует то же пространство имен и то же имя сборки. Скопируйте следующие параметры из Вашего .NET Framework проекта:
<PropertyGroup>
    <RootNamespace><!-- (Your default namespace) --></RootNamespace>
    <AssemblyName><!-- (Your assembly name) --></AssemblyName>
</PropertyGroup>
  • Отключите AssemblyInfo.cs файл. Как было отмечено ранее, в проектах нового стиля AssemblyInfo.cs генерируется автоматически по умолчанию. В то же время AssemblyInfo.cs файл из старого WinForms проекта будет скопирован и в новый проект, так как все ** \ *.cs файлы были связаны на предыдущем шаге. Это приведет к дублированию AssemblyInfo.cs. Чтобы избежать этого в файле MatchingGame.Core проекта, для GenerateAssemblyInfo было установлено значение false.
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  • Запустите новый проект. Установите новый .NET Core проект в качестве StartUp проекта и запустите его. Убедитесь, что все работает.
  • Скопируйте или оставьте ссылку. Теперь вместо связывания файлов можно скопировать их из старого .NET Framework UI проекта в новый .NET Core 3.0 UI проект. После этого предыдущий можно удалить.
Использование WinForms Designer для проектов .NET Core

Как было упомянуто выше, WinForms Designer для .NET Core проектов еще не доступен в Visual Studio. Однако есть два способа обойти данный вопрос: 

  1. Сохраните файлы связанными (просто не выполните предыдущий шаг) и скопируйте их, когда будет доступна designer поддержка. Таким образом, можно изменить файлы в старом .NET Framework WinForms проекте, используя designer. И изменения будут автоматически отражены в новом .NET Core WinForms проекте, поскольку они связаны между собой.
  2. Храните два файла проекта в той же директории, где и WinForms проект: старый .csproj файл из существующего .NET Framework проекта и новый .csproj файл в SDK-style нового .NET Core WinForms проекта. Нужно выгрузить и перезапустить проект с соответствующим файлом проекта в зависимости от того, хотите ли Вы использовать designer или нет.
Подведение итогов

В этой статье было показано, как перенести десктопное приложение, содержащее несколько проектов, из .NET Framework на NET Core. Обычно недостаточно просто перенастроить свои проекты на .NET Core. Также были описаны потенциальные проблемы, с которыми Вы можете столкнуться, и способы их решения. Кроме того, было рассмотрено, как можно использовать WinForms designer для портированных приложений, пока он еще не доступен для .NET Core проектов.



Exception: Object reference not set to an instance of an object.
Posted on 23. December 2018

.NET производительность в предварительной версии Visual Studio 2019

Наверняка Вы уже слышали о выпуске предварительного просмотра Visual Studio 2019. В этой статье будут рассмотрены улучшения для .NET разработчиков, которые затронули в первую очередь производительность. Полный список изменений можно найти здесь.

Поддержка Языка Regex

У постоянных выражений файлов в C# или Visual Basic теперь есть подсветка синтаксиса, диагностика компилятора и исправления кода. Эта поддержка синтаксического анализа распознает строки, переданные в regex конструктор, и строки, непосредственно предшествующие комментарию, содержащему строку `language=regex`. В этот выпуск включены следующие функции языка: классификация, сопоставление скобок, подсветка ссылок и диагностика компилятора.

Экспорт настроек редактора в Editorconfig

Теперь можно экспортировать настройки редактора в Editorconfig файл через Сервис> Параметры> Текстовый редактор> C#> Стиль кода с помощью кнопки «Создать .editorconfig файл, используя настройки».

Исправления и Рефакторинги Кода

В первом предварительном просмотре Visual Studio 2019 были добавлены несколько наиболее востребованных исправлений и рефакторингов кода. Рефакторинг и быстрые действия доступны с помощью горячих клавиш (Ctrl +.) или (Alt + Enter).

Foreach цикл для LINQ запроса

Foreach циклы к LINQ запросам или LINQ методам теперь объединяются с другими параметрами рефакторинга цикла, включая преобразование LINQ в Foreach цикл, For в Foreach цикл и Foreach в For цикл.


Добавление «Using» оператора к copy/paste


Преобразование анонимного типа в класс


Преобразование локальной функции в метод


Более новые исправления кода и рефакторинги:
  • Преобразование кортежа в именованную структуру.
  • Анализ мертвого кода неиспользуемых закрытых элементов с необязательным исправлением кода для удаления объявления неиспользованного элемента.
  • Создание метода деконструкции.
  • Добавление «await» там, где это подразумевается, но где нет предупреждения компилятора.
Ознакомиться со всеми исправлениями кода и рефакторингами для .NET можно здесь.

Индикатор работоспособности документа

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

«Find All» ссылки и поддержка CodeLens Razor

«Find All» ссылки (Shift-F12) и CodeLens отображают результаты из Razor (.cshtml) файлов в .NET Core проектах. Теперь Вы можете перейти к указанному коду в соответствующих Razor файлах.

Запуск тестов из Solution Explorer

Чтобы запустить или отладить тесты, можно просто щелкнуть правой кнопкой мыши на тестах, тестовых классах или тестовых проектах в Solution Explorer.

Попробуйте, как работает предварительный просмотр, оставляйте Ваши отзывы или вопросы о Visual Studio. В этом блог посте Вы можете изучить еще больше информации, а также посмотреть видео о других функциях Visual Studio 2019 Preview 1.

 



Exception: Object reference not set to an instance of an object.