Posted on 21. March 2020

Обновление конструктора .NET Core Windows Forms

Microsoft выпустили предварительную версию Visual Studio 16.6 - Visual Studio 2019 версии 16.6 Preview 1 и вместе с ней новую версию .NET Core конструктора Windows Forms.

 

В этом релизе представлено

Поддержка следующих элементов управления:

·      FlowLayoutPanel,

·      GroupBox,

·      ImageList,

·      MenuStrip (через PropertyBrowser и контекстное меню),

·      Panel,

·      SplitContainer,

·      Splitter,

·      TabControl,

·      TableLayoutPanel,

·      ToolStrip (через  PropertyBrowser, контекстное меню  и дизайнерские действия).

○ Локальные ресурсы и локализованные формы были включены в конструкторе.

○ Поддержка для LayoutMode и ShowGrid/SnapToGrid настроек через Tools->Options.

○ Улучшение производительности и точности.

○ Другие мелкие исправления и правки.

 

Последование

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

 

Как пользоваться конструктором

• Вам нужно воспользоваться Visual Studio Preview channel

•Нужно включить конструктор в Visual Studio. Перейдите по  Tools > Options > Environment > Preview Features и выбрать опцию Use the preview Windows Forms designer for .NET Core apps.

 

Как сообщить о проблемах

Ваше мнение очень важно для Microsoft! Пожалуйста, сообщайте о проблемах и отправляйте запросы функций через канал обратной связи Visual Studio. Используйте значок «Отправить отзыв» в правом верхнем углу Visual Studio, как показано ниже, и укажите, что он связан с областью «WinForms .NET Core».

Источник



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 хочет услышать ваш фидбек; команда будет рада, если вы опубликуете какие-либо мысли или результаты.


Источник



Posted on 18. March 2020

Анонс Entity Framework Core 5.0

https://devblogs.microsoft.com/dotnet/announcing-entity-framework-core-5-0-preview-1/

Microsoft объявила о первом анонсе EF Core 5.0.

Предпосылки

Для предварительного просмотра EF Core 5.0 требуется .NET Standard 2.1. Это означает:

• EF Core 5.0 работает на .NET Core 3.1; это не требует .NET 5.

○ Это может измениться в будущих превью в зависимости от того, как будет развиваться план для .NET 5.

EF Core 5.0 работает на других платформах, поддерживающих .NET Standard 2.1.

EF Core 5.0 не будет работать на платформах .NET Standard 2.0, включая .NET Framework.

Как скачать EF Core 5.0?

EF Core распространяется исключительно как набор пакетов NuGet. Например, чтобы добавить поставщика SQL Server в свой проект, вы можете использовать следующую команду с помощью инструмента dotnet:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 5.0.0-preview.2.20120.8

 

Пакеты EF Core, опубликованные сегодня:

Microsoft.EntityFrameworkCore - основной пакет EF Core

Microsoft.EntityFrameworkCore.SqlServer - поставщик базы данных для Microsoft SQL Server и SQL Azure

Microsoft.EntityFrameworkCore.Sqlite - поставщик базы данных для SQLite

Microsoft.EntityFrameworkCore.Cosmos - поставщик базы данных для Azure Cosmos DB

Microsoft.EntityFrameworkCore.InMemory - поставщик базы данных в памяти

Microsoft.EntityFrameworkCore.Tools - команды EF Core PowerShell для консоли диспетчера пакетов Visual Studio

Microsoft.EntityFrameworkCore.Design - общие компоненты времени разработки для инструментов EF Core

Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite - поддержка SQL Server для пространственных типов

Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite - поддержка SQLite для пространственных типов

Microsoft.EntityFrameworkCore.Proxies - загрузка и отслеживание изменений прокси

Microsoft.EntityFrameworkCore.Abstractions - разделенные EF Core абстракции

Microsoft.EntityFrameworkCore.Relational - общие компоненты EF Core для поставщиков реляционных баз данных

Microsoft.EntityFrameworkCore.Analyzers - анализаторы C # для EF Core

Microsoft.EntityFrameworkCore.Sqlite.Core - поставщик базы данных для SQLite без упакованного собственного двоичного файла

Microsoft опубликовали анонс о Framework Core 5.0 от  поставщика ADO.NET Microsoft.Data.Sqlite.Core.

Установка dotnet ef

Как и в случае EF Core 3.0 и 3.1, инструмент командной строки dotnet ef больше не включается в .NET Core SDK. Прежде чем вы сможете выполнить команды переноса EF Core или создания лесов, вам необходимо установить этот пакет как глобальный или локальный инструмент.

Чтобы установить инструмент предварительного просмотра глобально, сначала удалите любую существующую версию с помощью:

dotnet tool uninstall --global dotnet-ef

 

Затем установите с помощью:

dotnet tool install --global dotnet-ef --version 5.0.0-preview.2.20120.8

Эту новую версию dotnet ef можно использовать с проектами, в которых используются более старые версии среды выполнения EF Core.

Номера версий пакетов

В процессе сборки .NET 5 произошла ошибка, в результате которой пакеты EF preview 1 были ошибочно помечены как «5.0.0-preview.2.20120.8».

Это не должно иметь никакого функционального воздействия и не должно повлиять на Preview 2, который все еще запланирован на конец года.

Что нового в EF Core 5 Preview 1

Microsoft поддерживаем документацию новых функциях, представленных в каждом предварительном просмотре.

Некоторые из основных моментов из предварительного просмотра 1 вызываются ниже.

Простая регистрация

Эта опция функционально похожа на Database.Log в EF6. Таким образом, он предоставляет простой способ получения журналов из EF Core без необходимости настройки какого-либо внешнего каркаса ведения журналов.

EF Core заменяет Database.Log методом LogTo, вызываемым для DbContextOptionsBuilder в AddDbContext или OnConfiguring. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

 

Существуют перегрузки для:

• Установите минимальный уровень журнала

Пример: .LogTo(Console.WriteLine, LogLevel.Information)

• Фильтр только для определенных событий

Пример: .LogTo(Console.WriteLine, new[] {CoreEventId.ContextInitialized, RelationalEventId.CommandExecuted})

• Фильтр для всех событий в определенных категориях:

Пример: .LogTo(Console.WriteLine, new[] {DbLoggerCategory.Database.Name}, LogLevel.Information)

• Используйте пользовательский фильтр по событию и уровню:

Пример: .LogTo(Console.WriteLine, (id, level) => id == RelationalEventId.CommandExecuting)

 

Формат вывода может быть минимально сконфигурирован (API постоянно меняется), но вывод по умолчанию выглядит примерно так:

warn: 12/5/2019 09:57:47.574 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure)
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data, this mode should only be enabled during development.
dbug: 12/5/2019 09:57:47.581 CoreEventId.ShadowPropertyCreated[10600] (Microsoft.EntityFrameworkCore.Model.Validation)
      The property 'BlogId' on entity type 'Post' was created in shadow state because there are no eligible CLR members with a matching name.
info: 12/5/2019 09:57:47.618 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure)
      Entity Framework Core 5.0.0-dev initialized 'BloggingContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: SensitiveDataLoggingEnabled
dbug: 12/5/2019 09:57:47.644 CoreEventId.ValueGenerated[10808] (Microsoft.EntityFrameworkCore.ChangeTracking)
      'BloggingContext' generated temporary value '-2147482647' for the 'Id' property of new 'Blog' entity.
...

 

Простой способ получить сгенерированный SQL

В EF Core 5.0 представлен метод расширения ToQueryString, который будет возвращать SQL, который EF Core сгенерирует при выполнении запроса LINQ. Например, код:

var query = context.Set&lt;customer>().Where(c => c.City == city);
Console.WriteLine(query.ToQueryString())

приводит к таким выводам при использовании поставщика базы данных SQL Server:

DECLARE p0 nvarchar(4000) = N'London';
 
SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
WHERE [c].[City] = @__city_0

 

Обратите внимание, что объявления для параметров правильного типа также включены в вывод. Это позволяет копировать / вставлять в SQL Server Management Studio или аналогичные инструменты, так что запрос может быть выполнен для отладки / анализа.

Используйте атрибут C #, чтобы указать, что у объекта нет ключа

Тип объекта теперь можно настроить как “не имеющий ключа”, используя новый KeylessAttribute. Например:

[Keyless]
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public int Zip { get; set; }
}

 

Соединение или строка соединения могут быть изменены при инициализации DbContext

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

Прокси отслеживания изменений

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

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

Представления отладки - это простой способ взглянуть на внутренности EF Core при отладке проблем. Представление отладки для Модели было реализовано. Для EF Core 5.0 Microsoft  также упростили представление модели и добавили новое представление отладки для отслеживаемых объектов в диспетчере состояний.

Модель отладки

Разверните свойство Model объекта DbContext в выбранном отладчике и раскройте свойство DebugView.

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

Model:

  EntityType: Chassis

    Properties:

      TeamId (int) Required PK FK AfterSave:Throw

      Name (string)

      Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate

    Navigations:

      Team (_team, Team) ToPrincipal Team Inverse: Chassis PropertyAccessMode.Field

    Keys:

      TeamId PK

    Foreign keys:

      Chassis {'TeamId'} -> Team {'Id'} Unique ToDependent: Chassis ToPrincipal: Team

  EntityType: Driver

    Properties:

      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd

      CarNumber (Nullable<int>)

      Championships (int) Required

      Discriminator (no field, string) Shadow Required

      FastestLaps (int) Required

      Name (string)

      Podiums (int) Required

      Poles (int) Required

      Races (int) Required

      TeamId (int) Required FK Index

      Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate

      Wins (int) Required

    Navigations:

      Team (_team, Team) ToPrincipal Team Inverse: Drivers PropertyAccessMode.Field

    Keys:

      Id PK

    Foreign keys:

      Driver {'TeamId'} -> Team {'Id'} ToDependent: Drivers ToPrincipal: Team

    Indexes:

      TeamId

  EntityType: Engine

    Properties:

      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd

      EngineSupplierId (int) Required FK Index Concurrency

      Name (string) Concurrency

    Navigations:

      EngineSupplier (_engineSupplier, EngineSupplier) ToPrincipal EngineSupplier Inverse: Engines PropertyAccessMode.Field

      Gearboxes (_gearboxes, ICollection<gearbox>) Collection ToDependent Gearbox PropertyAccessMode.Field

      StorageLocation (Location) ToDependent Location PropertyAccessMode.Field

      Teams (_teams, ICollection<team>) Collection ToDependent Team Inverse: Engine PropertyAccessMode.Field

    Keys:

      Id PK

    Foreign keys:

      Engine {'EngineSupplierId'} -> EngineSupplier {'Id'} ToDependent: Engines ToPrincipal: EngineSupplier

    Indexes:

      EngineSupplierId

  EntityType: EngineSupplier

    Properties:

      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd

      Name (string)

    Navigations:

      Engines (_engines, ICollection<engine>) Collection ToDependent Engine Inverse: EngineSupplier PropertyAccessMode.Field

    Keys:

      Id PK

  EntityType: Gearbox

    Properties:

      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd

      EngineId (no field, Nullable<int>) Shadow FK Index

      Name (string)

    Keys:

      Id PK

    Foreign keys:

      Gearbox {'EngineId'} -> Engine {'Id'} ToDependent: Gearboxes

    Indexes:

      EngineId

  EntityType: Location

    Properties:

      EngineId (no field, int) Shadow Required PK FK AfterSave:Throw ValueGenerated.OnAdd

      Latitude (double) Required Concurrency

      Longitude (double) Required Concurrency

    Keys:

      EngineId PK

    Foreign keys:

      Location {'EngineId'} -> Engine {'Id'} Unique Ownership ToDependent: StorageLocation

  EntityType: Sponsor

    Properties:

      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd

      ClientToken (no field, Nullable</int><int>) Shadow Concurrency

      Discriminator (no field, string) Shadow Required

      Name (string)

      Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate

    Keys:

      Id PK

  EntityType: SponsorDetails

    Properties:

      TitleSponsorId (no field, int) Shadow Required PK FK AfterSave:Throw ValueGenerated.OnAdd

      ClientToken (no field, Nullable</int><int>) Shadow Concurrency

      Days (int) Required

      Space (decimal) Required

      Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate

    Keys:

      TitleSponsorId PK

    Foreign keys:

      SponsorDetails {'TitleSponsorId'} -> TitleSponsor {'Id'} Unique Ownership ToDependent: Details

  EntityType: Team

    Properties:

      Id (int) Required PK AfterSave:Throw

      Constructor (string)

      ConstructorsChampionships (int) Required

      DriversChampionships (int) Required

      EngineId (no field, Nullable</int><int>) Shadow FK Index

      FastestLaps (int) Required

      GearboxId (Nullable</int><int>) FK Index

      Name (string)

      Poles (int) Required

      Principal (string)

      Races (int) Required

      Tire (string)

      Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate

      Victories (int) Required

    Navigations:

      Chassis (_chassis, Chassis) ToDependent Chassis Inverse: Team PropertyAccessMode.Field

      Drivers (_drivers, ICollection<driver>) Collection ToDependent Driver Inverse: Team PropertyAccessMode.Field

      Engine (_engine, Engine) ToPrincipal Engine Inverse: Teams PropertyAccessMode.Field

      Gearbox (_gearbox, Gearbox) ToPrincipal Gearbox PropertyAccessMode.Field

    Keys:

      Id PK

    Foreign keys:

      Team {'EngineId'} -> Engine {'Id'} ToDependent: Teams ToPrincipal: Engine

      Team {'GearboxId'} -> Gearbox {'Id'} Unique ToPrincipal: Gearbox

    Indexes:

      EngineId

      GearboxId Unique

  EntityType: TestDriver Base: Driver

  EntityType: TitleSponsor Base: Sponsor

    Navigations:

      Details (_details, SponsorDetails) ToDependent SponsorDetails PropertyAccessMode.Field

 

Менеджер “debug view

Состояние менеджера немного скрыто, чем модель. Чтобы найти его, перейдите в свойство ChangeTracker объекта DbContext в выбранном вами отладчике, а затем посмотрите в свойстве StateManager  и разверните DebugView.

Краткий обзор менеджера отображает:

• Каждый объект отслеживается

• Значение первичного ключа

• Состояние объекта: добавлено, не изменено, изменено или удалено.

• Значения свойства внешнего ключа

Например:

Engine (Shared) {Id: 1} Unchanged FK {EngineSupplierId: 1}
Location (Shared) {EngineId: 1} Unchanged FK {EngineId: 1}
Team (Shared) {Id: 4} Modified FK {EngineId: 1} FK {GearboxId: <null>}

 

Длинный вид показывает все в коротком виде:

• Текущее значение каждого свойства

• Независимо от того, помечено ли свойство как измененное

• Исходное значение свойства, если оно отличается от текущего значения

• Сущность, на которую ссылается ссылочная навигация с использованием значения первичного ключа ссылочной сущности

• Список объектов, на которые ссылается навигация по коллекции, снова используя значения первичного ключа

Например:

Engine (Shared) {Id: 1} Unchanged
  Id: 1 PK
  EngineSupplierId: 1 FK
  Name: 'FO 108X'
  EngineSupplier: <null>
  Gearboxes: </null><null>
  StorageLocation: {EngineId: 1}
  Teams: [{Id: 4}]
Location (Shared) {EngineId: 1} Unchanged
  EngineId: 1 PK FK
  Latitude: 47.64491
  Longitude: -122.128101
Team (Shared) {Id: 4} Modified
  Id: 4 PK
  Constructor: 'Ferrari'
  ConstructorsChampionships: 16
  DriversChampionships: 15
  EngineId: 1 FK Modified Originally 3
  FastestLaps: 221
  GearboxId: </null><null> FK
  Name: 'Scuderia Ferrari Marlboro'
  Poles: 203
  Principal: 'Stefano Domenicali'
  Races: 805
  Tire: 'Bridgestone'
  Version: '0x000000000001405A'
  Victories: 212
  Chassis: </null><null>
  Drivers: []
  Engine: {Id: 1}
  Gearbox: </null><null>

 

Улучшена обработка нулевой семантики базы данных

Реляционные базы данных обычно обрабатывают NULL как неизвестное значение и, следовательно, не равны никаким другим NULL. C #, с другой стороны, рассматривает нулл как определенное значение, которое сравнивается с любым другим нулл. EF Core по умолчанию переводит запросы так, чтобы они использовали нулевую семантику C #. EF Core 5.0 значительно повышает эффективность этих переводов.

Свойства индексатора

EF Core 5.0 поддерживает отображение свойств индексатора C #. Это позволяет подразделениям действовать как пакеты свойств, в которых столбцы сопоставляются с именованными свойствами в пакете.

Генерация проверочных ограничений для отображений enum

Миграции EF Core 5.0 теперь могут генерировать ограничения CHECK для сопоставлений свойств перечисления. Например:

EnumColumn VARCHAR(10) NOT NULL CHECK (MyEnumColumn IN('Useful', 'Useless', 'Unknown'))

 

IsRelational

Новый метод IsRelational был добавлен в дополнение к существующим IsSqlServerIsSqlite и IsInMemory.

Это можно использовать для проверки, использует ли DbContext какой-либо поставщик реляционных баз данных. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    if (Database.IsRelational())
    {
        // Do relational-specific model configuration.
    }
}

 

Советующая поддержка  Cosmos с ETags

Поставщик базы данных Azure Cosmos DB теперь поддерживает ETags. Используйте конструктор моделей в OnModelCreating для настройки ETag:

builder.Entity&lt;customer>().Property(c => c.ETag).IsEtagConcurrency();

 

Затем SaveChanges генерирует исключение DbUpdateConcurrencyException при конфликте параллелизма, который может быть обработан для реализации повторных попыток и т. Д.

Как запросить переводы для большего количества конструкций DateTime?

Запросы, содержащие новую конструкцию DateTime, теперь переведены.

Кроме того, теперь сопоставлены следующие функции SQL Server: * DateDiffWeek * DateFromParts

Например:

var count = context.Orders.Count(c => date > EF.Functions.DateFromParts(DateTime.Now.Year, 12, 25));

 

Перевод запросов для большего количества массива байтов

Запросы, использующие свойства Contains, Length, SequenceEqual и т. Д. В byte [], теперь переводятся в SQL. Например:

var blogs = context.Blogs.Where(e => e.Picture.Contains((byte)127)).ToList();

 

Перевод запроса для реверса

Запросы с использованием Reverse теперь переведены. Например:

context.Employees.OrderBy(e => e.EmployeeID).Reverse()

 

Запрос для битовых операторов

Запросы с использованием битовых операторов теперь транслируются в большем количестве случаев. Например:

context.Orders.Where(o => ~o.OrderID == negatedId)

 

Перевод запроса на строки в Cosmos

Запросы, использующие строковые методы Contains, StartsWith и EndsWith, теперь переводятся при использовании поставщика Azure Cosmos DB.

Ежедневные сборки

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

Как и в случае предварительного просмотра, для ежедневных сборок не требуется .NET 5; их можно использовать с GA / RTM-версией .NET Core 3.1.

Документация и отзывы

Отправной точкой для всей документации EF Core является docs.microsoft.com/ef/core/.

Пожалуйста, сообщайте о найденных проблемах и любые другие отзывы dotnet/efcore GitHub repo.

Спасибо команде!

Большое спасибо команде EF всем, кто использовал EF на протяжении многих лет!

Спасибо авторам!

Большое спасибо следующим членам сообщества, которые уже предоставили код или документацию для выпуска EF Core 5!


 

Источник



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 Мы внимательно прислушиваемся к вашим отзывам и будем вносить изменения.

 

Источник



Posted on 14. December 2019

ConfigureAwait FAQ

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

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

Чтобы действительно понять ConfigureAwait, нам нужно начать немного раньше ...

Что такое SynchronizationContext?

 System.Threading.SynchronizationContext docs утверждают, что он «обеспечивает основные функциональные возможности для распространения контекста синхронизации в различных моделях синхронизации». Не совсем очевидное описание.

В 99,9% случав использования SynchronizationContext - это просто тип, который предоставляющий виртуальный метод Post, который делегирует асинхронное выполнение (существует множество других виртуальных членов в SynchronizationContext, (они менее используемые). Post базового типа, буквально вызывает ThreadPool.QueueUserWorkItem для асинхронного вызова предоставленного делегата. Однако производные типы переопределяют Post, чтобы разрешить выполнение этого делегата в наиболее подходящем месте и в наиболее подходящее время.

Например, Windows Forms имеет производный от SynchronizationContext тип, который переопределяет Post, чтобы сделать эквивалент Control.BeginInvoke; это означает, что делегат будет вызван в какой-то более поздний момент в потоке, связанном с этим соответствующим элементом управления, иначе говоря, «the UI thread». Windows Forms полагается на обработку сообщений Win32 и имеет «message loop», работающий в потоке пользовательского интерфейса, который просто ожидает поступления новых сообщений для обработки. Эти сообщения могут быть для движений и щелчков мыши, для ввода с клавиатуры, для системных событий, для делегатов, доступных для вызова и т. Д. Таким образом, с учетом экземпляра SynchronizationContext для потока пользовательского интерфейса приложений Windows Forms, чтобы получить делегата для выполнения на этот поток пользовательского интерфейса, просто нужно передать его в Post.

То же самое касается Windows Presentation Foundation (WPF). Он имеет свой собственный производный от SynchronizationContext тип с переопределением Post, которое аналогично «marshals» делегат в потоке пользовательского интерфейса (через Dispatcher.BeginInvoke), в данном случае управляемый диспетчером WPF, а не элементом управления Windows Forms.

И для Windows RunTime (WinRT). Он имеет свой собственный производный от SynchronizationContext тип с переопределением Post, который также ставит делегата в очередь потока пользовательского интерфейса через его CoreDispatcher.

Это выходит за рамки простого «run this delegate on the UI thread». Любой может реализовать SynchronizationContext с Post, который делает все что угодно. Например, мне может быть все равно, в каком потоке работает делегат, но я хочу убедиться, что все делегаты Post моего SynchronizationContext выполняются с некоторой ограниченной степенью параллелизма. Я могу добиться этого с помощью специального SynchronizationContext, например:

Фактически, среда модульного тестирования xunit предоставляет SynchronizationContext, очень похожий на этот, который он использует для ограничения объема кода, связанного с тестами, которые могут выполняться одновременно.

Преимущество является в том, что, как и с любой абстракцией: он предоставляет единый API, который можно использовать для постановки в очередь делегата для обработки, как этого захочет создатель реализации. Итак, если я пишу библиотеку, и я хочу уйти и выполнить некоторую работу, а затем поставить делегата в очередь обратно в «context» исходного местоположения, мне просто нужно взять их SynchronizationContext, удержать его, а затем когда закончите свою работу, вызовите Post в этом контексте, чтобы передать делегата. Мне не нужно знать, что для Windows Forms я должен взять Control  и использовать его BeginInvoke, или для WPF я должен взять Dispatcher  и использовать его BeginInvoke, или для xunit я должен каким-то образом получить его контекст и очередь к нему; Мне просто нужно взять текущий SynchronizationContext  и использовать его позже. Чтобы достичь этого, SynchronizationContext  предоставляет свойство Current, так что для достижения вышеупомянутой цели я мог бы написать такой код:

Фреймворк, который хочет предоставить пользовательский контекст из Current , использует метод SynchronizationContext.SetSynchronizationContext.

Что такое TaskScheduler?

Synchronization Context - это общая абстракция для «scheduler». Отдельные структуры иногда имеют свои собственные абстракции для планировщика, и System.Threading.Tasks не является исключением. Когда задачи поддерживаются делегатом, так что они могут быть поставлены в очередь и выполнены, они связаны с System.Threading.Tasks.TaskScheduler. Так же, как SynchronizationContext предоставляет виртуальный метод Post для постановки в очередь вызова делегата (с реализацией, позже вызывающей делегат через типичные механизмы вызова делегата), TaskScheduler  предоставляет абстрактный метод QueueTask  (с реализацией, позже вызывающей эту задачу через метод ExecuteTask ).

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

Тип System.Threading.Tasks.ConcurrentExclusiveSchedulerPair. Экземпляр этого класса предоставляет два свойства TaskScheduler, одно из которых называется ExclusiveScheduler , а другое - ConcurrentScheduler. Задачи, запланированные для ConcurrentScheduler , могут выполняться одновременно, но при условии ограничения, предоставленного ConcurrentExclusiveSchedulerPair при его создании (аналогично MaxConcurrencySynchronizationContext , показанного ранее), и никакие задачи ConcurrentScheduler  не будут выполняться, когда выполняется задача, запланированная для ExclusiveScheduler.

Как и SynchronizationContext, TaskScheduler также имеет свойство Current, которое возвращает «current» TaskScheduler. Однако, в отличие от SynchronizationContext, здесь нет способа настроить текущего планировщика. Вместо этого текущий планировщик - тот, который связан с текущей выполняющимся Task, и планировщик предоставляется системе как часть запуска Task. Так, например, эта программа выведет «True», так как лямбда, используемая с StartNew , выполняется в ConcurrentExclusiveSchedulerPair ExclusiveScheduler и увидит TaskScheduler.Current, установленный в этот планировщик:

Интересно, что TaskScheduler предоставляет статический метод FromCurrentSynchronizationContext, который создает новый TaskScheduler, который ставит в очередь Task для выполнения на любом возвращаемом SynchronizationContext.Current, используя метод Post  для постановки в очередь задач.

Как SynchronizationContext и TaskScheduler связаны с ожиданием?

Подумайте о написании приложения UI с помощью Button. П Нажав на Button, мы хотим загрузить текст с веб-сайта и установить его в качестве Button Content. Доступ к Button возможен только из потока   UI, которому она принадлежит, когда мы успешно загрузили новый период, временный текст и хотим сохранить его обратно в ButtonContent нам нужно сделать это из потока, которому принадлежит элемент управления. Если мы этого не сделаем, мы получим отклонение, например:

System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'

 

Если бы мы писали это вручную, мы могли бы использовать SynchronizationContext, как показано ранее, чтобы перенаправить настройку Content обратно в исходный контекст, например через TaskScheduler:

private static readonly HttpClient s_httpClient = new HttpClient();
 
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
    s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask =>
    {
        downloadBtn.Content = downloadTask.Result;
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

 

или используя SynchronizationContext напрямую:

private static readonly HttpClient s_httpClient = new HttpClient();
 
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
    SynchronizationContext sc = SynchronizationContext.Current;
    s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask =>
    {
        sc.Post(delegate
        {
            downloadBtn.Content = downloadTask.Result;
        }, null);
    });
}

 

Оба этих подхода, тем не менее, явно используют обратные вызовы. Вместо этого мы хотели бы написать код естественным образом с помощью async/await:

private static readonly HttpClient s_httpClient = new HttpClient();
 
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
    string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
    downloadBtn.Content = text;
}

 

Это «просто работает», при успешной установке Content в потоке UI, потому что, как и в случае с ручной реализованной версией, await Task по умолчанию обращает внимание на SynchronizationContext.Current, а также  TaskScheduler.Current Когда вы await чего-либо в C #, компилятор преобразует код для запроса (посредством вызова GetAwaiter) «ожидаемого» (в данном случае, Task) для «ожидающего» (в данном случае TaskAwaiter<string>). Этот ожидающий отвечает за подключение обратного вызова (часто называемого «продолжением»), который будет вызывать обратный вызов в конечный автомат после завершения ожидаемого объекта, и он делает это с использованием любого контекста / планировщика, захваченного во время обратного вызова. Хотя не совсем используемый код (используются дополнительные оптимизации и настройки), он выглядит примерно так:

object scheduler = SynchronizationContext.Current;
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
    scheduler = TaskScheduler.Current;
}

 

Другими словами, он сначала проверяет, установлен ли SynchronizationContext, если его нету, независимо от запуска TaskScheduler выполняется по умолчанию. Если он сочтет, что обратный вызов готов к запуску он будет использовать фиксированнй планировщик; в противном случае он обычно выполняет обратный вызов как часть операции, завершающей ожидаемую задачу.

Что делает ConfigureAwait (false)?

Метод ConfigureAwait не является особенным: он не распознается каким-либо особым компилятором или средой выполнения. Это просто метод, который возвращает структуру (ConfiguredTaskAwaitable), которая возвращает исходную задачу, к которой она была вызвана, а также указанное логическое значение. Помните, что await может использоваться с любым типом, который выставляет правильный паттерн. Возвращая другой тип, это означает что когда компилятор получает доступ к методу GetAwaiter  (часть паттерна), он делает это вне типа, непосредственно из задачи.

В частности, ожидание типа, возвращенного из ConfigureAwait(continueOnCapturedContext: false) вместо ожидания Task, напрямую заканчивается воздействием на логику, показанную ранее для того, как захватывается целевой контекст / планировщик. Это эффективно делает ранее показанную логику более похожей на это:

object scheduler = null;
if (continueOnCapturedContext)
{
    scheduler = SynchronizationContext.Current;
    if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
    {
        scheduler = TaskScheduler.Current;
    }
}

 

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

Почему нужно использовать ConfigureAwait (false)?

ConfigureAwait(continueOnCapturedContext: false) используется, чтобы не вызывать обратный вызов в исходном контексте или планировщике. Это имеет несколько преимуществ:

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

 

Для очень срочных путей даже дополнительные затраты на проверку текущего  SynchronizationContext и текущего TaskScheduler (оба из которых включают в себя доступ к статическим потокам) могут добавить накладные расходы. Если код после await на самом деле не требует выполнения в исходном контексте, использование ConfigureAwait(false) может избежать всех этих затрат: его не нужно ставить в очередь, он может использовать все возможные оптимизации.

Как избежать тупиков. Рассмотрим библиотечный метод, который использует await в результате некоторой загрузки по сети. Вы вызываете этот метод и синхронно блокируете ожидание его завершения, например, с помощью .Wait()или .Result или .GetAwaiter().GetResult() выключить возвращенный объект Task.

  Теперь рассмотрим, что произойдет, если ваш вызов этого произойдет, если текущий SynchronizationContext ограничивает число операций, которые могут быть запущены на нем, до 1, будь то явно с помощью чего-то вроде MaxConcurrencySynchronizationContext, показанного ранее, или неявным образом из-за того, что это контекст, который только имеет одну нить, которую можно использовать, например, поток пользовательского интерфейса. Таким образом, вы вызываете метод в этом одном потоке, а затем блокируете его, ожидая завершения операции. Операция запускает загрузку по сети и ожидает ее. Так как ожидание Задачи по умолчанию захватывает текущий SynchronizationContext, он делает это, и когда загрузка по сети завершается, он помещает обратно в SynchronizationContext обратный вызов, который вызовет оставшуюся часть операции. Но единственный поток, который может обработать обратный вызов в очереди, в настоящее время заблокирован блокировкой кода, ожидающей завершения операции. И эта операция не завершится, пока не будет обработан обратный вызов. Тупик! Это может применяться, даже когда контекст не ограничивает параллелизм только 1, но когда ресурсы ограничены. Представьте себе ту же ситуацию, за исключением использования MaxConcurrencySynchronizationContext с лимитом 4. И вместо того, чтобы делать только один вызов операции, мы ставим в очередь в этот контекст 4 вызова, каждый из которых делает вызов и блокирует ожидание его завершения. Теперь мы все еще заблокировали все ресурсы в ожидании завершения асинхронных методов, и единственное, что позволит этим асинхронным методам завершиться, - это если их обратные вызовы могут быть обработаны этим контекстом, который уже полностью используется. Опять тупик! Если бы вместо этого метод библиотеки использовал ConfigureAwait(false), он не поставил бы в очередь обратный вызов обратно в исходный контекст, избегая сценариев взаимоблокировки.

Почему я использую ConfigureAwait (true)?

ConfigureAwait(true) не делает ничего значимого. При сравнении await task с await task.ConfigureAwait(true). ConfigureAwait (true) они функционально идентичны. Если вы видите ConfigureAwait(true) в рабочем коде, вы можете удалить его без вреда для себя.

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

В каких случаях использовать ConfigureAwait (false)?

Это зависит от того, реализуете ли вы код уровня приложения или код библиотеки общего назначения?

При написании приложений вы обычно хотите действий по умолчанию. Если модель / среда приложения (например, Windows Forms, WPF, ASP.NET Core и т. Д.) Публикует пользовательский SynchronizationContext, почти наверняка есть веская причина для этого: он предоставляет способ для кода, который заботится о контексте синхронизации, взаимодействовать с моделью приложения / среды соответственно. Поэтому, если вы пишете обработчик событий в приложении Windows Forms, пишете модульный тест в xunit, пишете код в контроллере ASP.NET MVC, независимо от того, действительно ли модель приложения опубликовала SynchronizationContext, вы хотите использовать SynchronizationContext, если он существует. А это означает, что по умолчанию / ConfigureAwait(true). Вы просто используете await, и правильное осуществление происходит в отношении обратных вызовов / продолжений, отправляемых обратно в исходный контекст, если таковой существует. Это приводит к общему руководству: если вы пишете код уровня приложения, не используйте ConfigureAwait(false). Если вы вспомните пример кода обработчика события Click ранее в этом посте:

private static readonly HttpClient s_httpClient = new HttpClient();
 
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
    string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
    downloadBtn.Content = text;
}

 

установка downloadBtn.Content = text должна быть сделана обратно в исходном контексте. Если код нарушил это правило и вместо этого использовал ConfigureAwait(false) тогда он не должен иметь:

private static readonly HttpClient s_httpClient = new HttpClient();
 
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
    string text = await s_httpClient.GetStringAsync("http://example.com/currenttime").ConfigureAwait(false); // bug
    downloadBtn.Content = text;
}

 

То же самое относится и к коду в классическом приложении ASP.NET, зависящем от HttpContext.Current; использование ConfigureAwait(false), а затем использования HttpContext.Current, вероятно, приведет к проблемам.

В отличие от этого, библиотеки общего назначения являются «универсальными» отчасти потому, что их не волнует среда, в которой они используются. Вы можете использовать их из веб-приложения, из клиентского приложения или из теста, это не имеет значения, поскольку код библиотеки не зависит от модели приложения, в которой он может быть использован. Быть независимым также означает, что он не собирается делать что-то, что должно взаимодействовать с моделью приложения определенным образом, например, он не будет получать доступ к элементам управления UI, потому что библиотека общего назначения ничего не знает об элементах управления UI. Поскольку тогда нам не нужно запускать код в какой-либо конкретной среде, мы можем избежать принудительного возврата продолжений / обратных вызовов к исходному контексту, и мы делаем это, используя ConfigureAwait(false) и получая преимущества как в производительности, так и в надежности. Это приводит к общему руководству: если вы пишете код библиотеки общего назначения, используйте ConfigureAwait(false). Вот почему, например, вы увидите каждое (или почти каждое) await в библиотеках среды выполнения .NET Core, использующее ConfigureAwait(false) в каждом await; за некоторыми исключениями, в случаях, когда это не очень вероятно, ошибка может быть исправлена. Например, этот PR исправил отсутствующий вызов ConfigureAwait(false) в HttpClient.

Как и в случае со всеми указаниями, конечно, могут быть исключения, места, где это не имеет смысла. Например, одно из более крупных исключений (или, по крайней мере, категорий, требующих обдумывания) в библиотеках общего назначения - это когда в этих библиотеках есть API, которые принимают делегатов для вызова. В таких случаях абонент библиотеки проходит потенциально код приложения на уровень, который будет вызван в библиотеке, которая затем эффективно делает это «общее назначение» предположение библиотеки. Рассмотрим, например, асинхронную версию метода Where LINQ, например  public static async IAsyncEnumerable<T> WhereAsync(this IAsyncEnumerable<T> source, Func<T, bool> predicate). Нужно ли здесь вызывать predicate в исходном SynchronizationContext вызывающей стороны? Это зависит от реализации WhereAsync, и по этой причине он может отказаться от использования ConfigureAwait(false).

Даже в этих особых случаях общее руководство стоит и является очень хорошей отправной точкой: используйте ConfigureAwait(false), если вы пишете код общего назначения для библиотеки / приложения-модели.

Гарантирует ли ConfigureAwait (false), что обратный вызов не будет выполняться в исходном контексте?

Нет. Это гарантирует, что он не будет поставлен в очередь в исходный контекст ... но это не означает, что код после await task.ConfigureAwait(false) по-прежнему не будет работать в исходном контексте. Это связано с тем, что ожидающие не заставляют что-либо возвращаться в очередь. Таким образом, если вы ожидаете задачу, которая уже выполнена к тому времени, независимо от того, использовали ли вы ConfigureAwait(false), код сразу после этого продолжит выполняться в текущем потоке в любом контексте.

Заметным исключением является то, что вы знаете, что первое await всегда завершается асинхронно, а ожидаемое событие вызывает свой обратный вызов в среде, свободной от пользовательского SynchronizationContext  или TaskScheduler. Например, CryptoStream  в библиотеках времени выполнения .NET хочет, чтобы его потенциально вычислительно-интенсивный код не выполнялся как часть синхронного вызова вызывающей стороны, чтобы все после первого await  выполнялось в потоке резерва. Однако даже в этом случае вы заметите, что в следующем await все еще используется ConfigureAwait(false); технически это не является необходимым, но это делает обзор кода намного проще, так как в противном случае каждый раз, когда этот код просматривается, он не требует анализа, чтобы понять, почему ConfigureAwait(false) был отключен.

 

Можно ли применять Task.Run, чтобы избежать использования ConfigureAwait (false)?

Да. Если вы напишите:

Task.Run(async delegate
{
    await SomethingAsync(); // won't see the original context

затем ConfigureAwait(false) для этого вызова SomethingAsync() будет nop, поскольку делегат, переданный в Task.Run, будет выполняться в потоке пула потоков без кода пользователя выше в стеке, например SynchronizationContext.Current вернет null. Кроме того,  Task.Run неявно использует TaskScheduler.Default, что означает, что запрос TaskScheduler.Current  внутри делегата также вернет  Default. Это означает, что ожидание будет демонстрировать одинаковое действия независимо от того, использовался ли ConfigureAwait(false). Он также не дает никаких гарантий относительно того, что может делать код внутри этой лямбды. Если у вас есть код:

Task.Run(async delegate
{
    SynchronizationContext.SetSynchronizationContext(new SomeCoolSyncCtx());
    await SomethingAsync(); // will target SomeCoolSyncCtx
});

 

код внутри SomethingAsync на самом деле увидит SynchronizationContext.Current как тот экземпляр SomeCoolSyncCtx, и это await , и любые ненастроенные задачи внутри SomethingAsync отправят обратно на него. Таким образом, чтобы использовать этот подход, вам необходимо понять, что весь код, который вы ставите в очередь, может или не может делать, и могут ли его действия помешать вашему.

Этот подход также достигается за счет необходимости создавать / ставить в очередь дополнительный объект задачи. Это может иметь значения для вашего приложения или библиотеки в зависимости от вашей чувствительности к производительности.

Также имейте в виду, что такие уловки могут вызвать больше проблем, чем они стоят, и иметь другие непредвиденные последствия. Например, инструменты статического анализа (например, анализаторы Roslyn) были написаны для отметки ожидающих, которые не используют ConfigureAwait(false), таких как CA2007. Если вы включили такой анализатор, но затем применили хитрость, подобную этой, просто чтобы избежать использования ConfigureAwait, есть большая вероятность, что анализатор пометит его и фактически вызовет дополнительную работу для вас. Так что, возможно, вы затем отключите анализатор из-за его шумности, и теперь вы в конечном итоге пропустите другие места в кодовой базе, где вы на самом деле должны были использовать ConfigureAwait(false).

Можно ли использовать SynchronizationContext.SetSynchronizationContext, вместо ConfigureAwait (false)?

Это зависит от используемого кода.

Некоторые разработчики пишут такой код:

Task t;
SynchronizationContext old = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try
{
    t = CallCodeThatUsesAwaitAsync(); // awaits in here won't see the original context
}
finally { SynchronizationContext.SetSynchronizationContext(old); }
await t; // will still target the original context

 

в надежде, что он сделает код внутри CallCodeThatUsesAwaitAsync, увидит текущий контекст как null. Однако вышеприведенное не повлияет на ожидаемый файл TaskScheduler.Current, поэтому, если этот код выполняется на каком-то пользовательском TaskScheduler, await  внутри CallCodeThatUsesAwaitAsync (и не использующий ConfigureAwait(false)) все равно увидит и вернется в очередь к этому пользовательскому TaskScheduler.

Все те же предостережения также применимы, как и в предыдущем FAQ, связанном с Task.Run: такие обходные пути имеют практические последствия, и код внутри попытки может также помешать, установив другой контекст (или вызвав код не по умолчанию TaskScheduler).

При таком шаблоне вы также должны быть осторожны с небольшим изменением:

SynchronizationContext old = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try
{
    await t;
}
finally { SynchronizationContext.SetSynchronizationContext(old); }

 

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

Использование GetAwaiter (). GetResult (). Нужно ли использовать ConfigureAwait (false)?

Нет. ConfigureAwait влияет только на обратные вызовы. В частности, шаблон ожидающего требует, чтобы ожидающие предоставили свойство IsCompleted, метод GetResult  и метод OnCompleted  (необязательно с методом UnsafeOnCompleted ). ConfigureAwait влияет только на поведение  {Unsafe}OnCompleted, поэтому, если вы просто напрямую вызываете GetResult(), независимо от того, делаете ли вы это с помощью TaskAwaiter  или ConfiguredTaskAwaitable.ConfiguredTaskAwaiter. Поэтому, если вы видите в коде task.ConfigureAwait(false). GetAwaiter().GetResult(), вы можете заменить его на task.GetAwaiter().GetResult()  (а также подумать, действительно ли вы хотите блокировать подобное).

Я знаю, что работаю в среде, в которой никогда не будет настраиваемого SynchronizationContext или настраиваемого TaskScheduler. Могу ли пропустить, использование ConfigureAwait (false)?

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

Я слышал, что ConfigureAwait (false) больше не требуется в .NET Core. Правда?

Ложь. Он необходим при работе в .NET Core по тем же причинам, что и при работе в .NET Framework. В этом отношении ничего не изменилось.

Однако изменилось то, что публикуют определенные среды свой собственный SynchronizationContext. В частности, тогда как классический ASP.NET в .NET Framework имеет свой собственный SynchronizationContext, в отличие от ASP.NET Core нет. Это означает, что код, работающий в приложении ASP.NET Core по умолчанию, не увидит пользовательский SynchronizationContext, что уменьшает необходимость выполнения ConfigureAwait(false) в такой среде.

Однако это не означает, что пользовательский SynchronizationContext или TaskScheduler  никогда не будет представлен. Если какой-либо пользовательский код (или другой код библиотеки, используемый вашим приложением) устанавливает пользовательский контекст и вызывает ваш код или вызывает ваш код в Task, запланированной для пользовательского TaskScheduler, то даже в ASP.NET Core ваши ожидающие могут увидеть не контекст по умолчанию или планировщик, который заставит вас хотеть использовать ConfigureAwait(false). Конечно, в таких ситуациях, если вы избегаете синхронной блокировки (что вы должны избегать в веб-приложениях независимо от этого),  если вы не возражаете против небольших накладных расходов на производительность в таких ограниченных случаях, вы, вероятно, можете обойтись без использования ConfigureAwait(false).

Могу ли я использовать ConfigureAwait, когда ожидание по каждому элементу в IAsyncEnumerable?

Да. Посмотрите эту статью MSDN Magazine для примера.

await foreach привязывается к шаблону, и поэтому, хотя он может использоваться для перечисления IAsyncEnumerable<T>, он также может использоваться для перечисления чего-то, что предоставляет правильную площадь поверхности API. Библиотеки времени выполнения .NET включают метод расширения ConfigureAwait для IAsyncEnumerable<T>, который возвращает пользовательский тип, заключающий в себе IAsyncEnumerable<T> и Boolean, выставляет правильный шаблон. Когда компилятор генерирует вызовы методов MoveNextAsync и DisposeAsync, эти вызовы возвращаются к сконфигурированному типу структуры, и он, в свою очередь, выполняет ожидание желаемым сконфигурированным способом.

Могу ли я использовать ConfigureAwait, когда «ожидаю использования» IAsyncDisposable?

Да, хотя с небольшими сложностями.

Библиотеки времени выполнения .NET предоставляют метод расширения ConfigureAwait для IAsyncDisposable, и использование await using  будет успешно работать с этим, поскольку он реализует соответствующий шаблон (а именно, предоставляет соответствующий метод DisposeAsync ):

await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false))
{
    ...
}

 

Проблема здесь в том, что тип теперь не MyAsyncDisposableClass, а скорее System.Runtime.CompilerServices.ConfiguredAsyncDisposable, который является типом, возвращаемым из этого метода расширения ConfigureAwait  в IAsyncDisposable.

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

var c = new MyAsyncDisposableClass();
await using (c.ConfigureAwait(false))
{
    ...
}

 

Теперь тип c снова является желаемым MyAsyncDisposableClass. Это также имеет эффект увеличения области действия c; если это эффективно, вы можете обернуть все это в фигурные скобки.

Я использовал ConfigureAwait (false), но AsyncLocal все еще передавался в код после ожидания. Это ошибка?

Нет, это ожидаемо. Данные AsyncLocal<T> передаются как часть ExecutionContext, которая отделена от SynchronizationContext. Если вы явно не отключили поток ExecutionContext с помощью ExecutionContext.SuppressFlow(), ExecutionContext (и, следовательно, данные AsyncLocal<T>) всегда будут проходить через await, независимо от того, используется ли ConfigureAwait , чтобы избежать захвата исходного SynchronizationContext. Для получения дополнительной информации см. Этот пост в блоге.

Может ли язык помочь мне избежать необходимости использовать ConfigureAwait (false) в моей библиотеке?

Разработчики библиотек иногда выражают недовольство необходимостью использования ConfigureAwait(false) и просят о менее агрессивных альтернативах.

В настоящее время их нет, по крайней мере, они не встроены в язык / компилятор / среду выполнения. Однако существует множество предложений о том, как может выглядеть такое решение, например,

https://github.com/dotnet/csharplang/issues/645,

https://github.com/dotnet/csharplang/issues/2542,

https://github.com/dotnet/csharplang/issues/2649,

https://github.com/dotnet/csharplang/issues/2746.

Если это важно для вас, и вы чувствуете, что у вас есть новые и интересные идеи, можете поделиться своими мыслями.

 

Источник



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.

Источник

 



Posted on 15. November 2019

Анонс Windows Community Toolkit v6.0

Microsoft объявляет о следующем обновлении Windows Community Toolkit версии 6.0, которое стало возможным, благодаря помощи и вкладу нашего сообщества разработчиков. В этом выпуске реализована поддержка ARM64 для инструментария, а также обновление XAML Islands для поддержки .NET Core 3. Кроме того, у нас есть новые функции, такие как элемент управления "Eye Dropper " и новые помощники уведомлений Win32. У нас также есть обновление Microsoft Graph благодаря XAML контролам.


Смотрите более подробную информацию об этих функциях ниже.

XAML Islands приближает UWP к WPF, WinForms и Win32

XAML  Islands позволяет разработчику улучшить внешний вид и функциональность существующего приложения Win32 для WPF, Windows Forms или C ++, использовать новейшие функции пользовательского интерфейса Windows 10, которые доступны только через UWP контроллы, например, рукописный ввод:

 

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

 

Документация для XAML Islands.

 

Поддержка ARM64

Windows Community Toolkit теперь поддерживает приложения, которые ориентированные на ARM64. Что позволяет разработчикам использовать преимущества времени автономной работы и производительности, работая на собственной архитектуре для таких устройств, как Surface Pro X. Разработчики также тесно сотрудничали с командой Win2D, чтобы убедиться, что она поддерживает ARM64. Это было важно для Lottie и других функций инструментария, основанных на Win2D.

Улучшенная анимация Lottie

Обновление предоставляет больше возможностей Adobe After Effects для Lottie-Windows, в том числе Linear и Radial Gradients, Masks, Track Mattes поддержку codegen для Image Layers . Мы надеемся, что эти дополнения позволят дизайнерам и разработчикам приложений создавать еще более привлекательные визуальные интерфейсы для пользователей Windows 10. Поскольку некоторые из этих функций основаны на более новых SDK, Lottie-Windows теперь также предлагает адаптивное управление версиями. Мы рассчитываем на то, что сообщество определит приоритеты работы с функциями, поэтому, пожалуйста, продолжайте оставлять свои ценные отзывы и предложения для Lottie-Windows!

 

Eye Dropper

Новый элемент управления “Eye Dropper” позволяет вам легко и быстро выбирать цвет приложения.

Документация для EyeDropper.

 

Превью XAML Graph Controls

Новое дополнение к Windows Community Toolkit позволяет разработчикам легко проходить проверку подлинности и получать доступ к Microsoft Graph в приложениях Windows 10 для создания обширных данных. Эти элементы управления доступны в качестве предварительного просмотра версии 6.1 и будут работать с приложениями UWP и в приложениях WPF / WinForms для Win32 через XAML Islands в .NET Core 3. Кроме того, совсем скоро с помощью Xamarin и Uno Platform у вас появится возможность использовать их на Android и iOS.

 

Читайте об этих новых элементах управления в нашем официальном заявлении или на GitHub.

Начни сегодня

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

Напоминаем, что вы можете начать работу, следуя нашему учебному пособию docs.microsoft.com tutorial, или просмотреть последние функции, установив образец приложения Windows Community Toolkit из Магазина Microsoft. Если вы хотите внести свой вклад в разработку, пожалуйста, присоединяйтесь на GitHub! Чтобы присоединиться к беседе в Twitter, используйте хештег #WindowsToolkit.

Всем удачного кодирования!

Источник



Posted on 7. November 2019

.NET Core 3 для Windows Desktop

 

Предисловие

В сентябре Microsoft выпустили .NET Core поддерживающий создание настольных приложений Windows, включая WPF и Windows Forms. Мы счастливы наблюдать, за тем как многие разработчики делятся своими историями о переносе настольных приложений (и управляющих библиотек) в .NET Core. Комания постоянно слышит истории о том, что разработчики настольных систем на платформе .NET Windows поддерживают свой бизнес с помощью WPF и Windows Forms, особенно в тех случаях, когда настольные системы превосходны, включая

- UI-dense формы над данными приложений (FOD) 

- Оперативный интерфейс пользователя

- Приложения, которые могут работать в режиме оффлайн или быть выключены

- Устройства с приложениями, которые привязаны к драйверам пользователей

Это только начало разработки приложений для Windows на .NET Core. Читайте дальше, чтобы узнать больше о преимуществах .NET Core для создания приложений Windows.

 

Почему Windows Desktop на .NET Core?

.NET Core (и в будущем .NET 5, построенный на ведущих основах .NET Core) станет будущим .NET. Мы стремимся поддерживать .NET Framework долгие годы, однако он не будет получать никаких новых функций, мы их добавим только в .NET Core (в перспективе .NET 5). Чтобы улучшить стеки рабочих столов Windows и позволить разработчикам настольных компьютеров .NET получать выгоду от всех обновлений будущего, мы представили Windows Forms и WPF для .NET Core. Они по-прежнему останутся технологиями, предназначенными только для Windows, поскольку они тесно связаны с API-интерфейсами Windows. Но .NET Core, помимо кроссплатформенности, имеет множество других функций, которые могут улучшить настольные приложения.

Прежде всего, все улучшения и языковые функции будут добавлены только в .NET Core, а в будущем - в .NET 5. Хорошим примером здесь является C # 8, который стал доступен в .NET Core 3.0. Кроме того, .NET Core версии Windows Forms и WPF станут частью платформы .NET 5. Итак, перемещая   ваше приложение на .NET Core, вы готовите его к .NET 5.

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

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

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

- Сокращение размеров приложения. В .NET Core 3 мы представили новую функцию под названием компоновщик (также иногда называемую триммером), которая будет анализировать ваш код и включать в автономное направление только те сборки из .NET Core, которые необходимы для вашего приложения. Таким образом, все детали платформы, которые не используются в вашем случае, будут обрезаны.

- Отдельные .exe файлы. Вы можете поместить свое приложение и платформу .NET Core в один файл .exe.

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

 

Установив свойства <PublishSingleFile>, <RuntimeIdentifier> и <PublishTrimmed> в профиле публикации, вы сможете развернуть обрезанное автономное приложение в виде одного файла .exe, как показано в примере ниже.

<PropertyGroup>

    <OutputType>Exe</OutputType>

    <TargetFramework>netcoreapp3.0</TargetFramework>

    <PublishSingleFile>true</PublishSingleFile>

    <RuntimeIdentifier>win-x64</RuntimeIdentifier>

    <PublishTrimmed>true</PublishTrimmed>

</PropertyGroup>

 

Различия между рабочим столом .NET Framework и рабочим столом .NET Core

При разработке настольных приложений вы не заметите большой разницы между версиями WPF и Windows Forms .NET Framework и .NET Core. Часть наших усилий заключалась в том, чтобы обеспечить функциональный паритет между этими платформами в области настольных ПК и расширить возможности .NET Core в будущем.  Приложения WPF полностью поддерживаются в .NET Core и готовы для использования, пока мы работаем над незначительными обновлениями и улучшениями. Для Windows Forms часть времени выполнения полностью перенесена в .NET Core, и команда работает над расширением Windows Forms Designer. Мы планируем подготовить его к четвертому кварталу 2020 года, пока вы можете ознакомится с предварительной версией расширения в Visual Studio 16.4 Preview 3 или более поздней версией. Не забудьте установить флажок в меню Сервис-> Параметры-> Функции предварительного просмотра-> Использовать дизайнер предварительного просмотра Windows Forms для приложений .NET Core и перезапустить Visual Studio. Пожалуйста, имейте в виду, что над ним еще ведется разработка. 

Серьезные изменения

В .NET Framework и .NET Core произошли несколько серьезных изменений, но большая часть кода, связанного с областями Windows Forms и WPF, была перенесена в Core. Если вы использовали такие компоненты, как WCF Client, Code Access Security, App Domains, Interop и Remoting, вам потребуется реорганизовать код, если вы хотите переключиться на .NET Core.

Следует помнить еще одну вещь: пути вывода по умолчанию в .NET Core отличаются от путей в .NET Framework, поэтому, если в вашем коде есть некоторые предположения относительно структуры файлов / папок запущенного приложения, то, вероятно, что он выйдет из строя во время выполнения.

Также есть изменения в настройке функций .NET. вместо файла machine.config использует файл <something>.runtimeconfig.json,который поставляется вместе с приложением и имеет ту же общую цель и аналогичную информацию. Некоторые конфигурации, такие как system.diagnostics, system.net или system.servicemodel, не поддерживаются, поэтому файл конфигурации приложения не удастся загрузить, если он содержит какой-либо из этих разделов. Это изменение касается сценариев трассировки System.Diagnostics и клиентов WCF, которые обычно настраивались с использованием конфигурации XML ранее. В .NET Core вам нужно вместо этого настроить их в коде. Чтобы изменить поведение без перекомпиляции, рассмотрите возможность настройки типов трассировки и WCF, используя значения, загруженные из источника Microsoft.Extensions.Configuration или из appSettings. 

Вы можете найти больше информации о различиях между .NET Core и .NET Framework в документации.

 

Начало работы

Проверьте эти короткие видеоуроки:

- Начало работы с WPF на .NET Core

- Начало работы с Windows Forms в .NET Core

- Различия между .NET Core и .Net Framework, что выбрать для вашего приложения

 

Перенесение приложений с .NET Framework на .NET Core

Прежде всего, запустите Portability Analyzer, если необходимо, обновите свой код, чтобы обеспечить 100% совместимость с .NET Core. Вот инструкции по использованию Portability Analyzer. Мы рекомендуем использовать элемент управления исходным кодом или сделать резервную копию вашего кода, прежде чем вносить какие-либо изменения в свое приложение, если рефакторинг не будет идти так, как вы хотите, и вы решите вернуться к своему исходному коду.

Когда ваше приложение будет полностью совместимо с .NET Core, для его переноса. В качестве отправной точки вы можете попробовать инструмент, который мы создали, чтобы помочь автоматизировать преобразование ваших проектов .NET Framework в .NET Core. - try-convert.

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

Инструмент - try-convert.  попытается перенести файлы проекта старого стиля в новый SDK-стиль и перенастроить соответствующие проекты в .NET Core.

 

Для ваших библиотек мы оставляем вам возможность настроить таргетинг платформы: на .NET Core или .NET Standard. Вы можете указать его в файле проекта, обновив значение для <TargetFramework>. Библиотеки без .NET Core-Specific, таких как WPF или Windows Forms, могут получить преимущество от.NET Standard:

<TargetFramework>netstandard2.1</TargetFramework>

так что их могут использовать абоненты, ориентированные на множество различных платформ .NET. С другой стороны, если библиотека использует функцию, для которой требуется .NET Core (например, Windows desktop UI APIs), библиотека может ориентироваться на .NET Core:

<TargetFramework>netcoreapp3.0</TargetFramework>

try-convert - это глобальный инструмент, который вы можете установить на свой компьютер, а затем запросить из CLI:

C:\> try-convert -p <path to your project>

 

или

C:\> try-convert -w <path to your solution>

 

Как упоминалось ранее, если инструмент try-convert не работает, вот материалы о том, как переносить ваше приложение вручную.

Видео

- Пример простого переноса

- Пример расширенного переноса

Документация

- Пример простого переноса

- Пример расширенного переноса (часть 1, часть 2)

- Обзор процесса переноса

 

 

Источник



Posted on 17. October 2019

Windows 10, версия 1909 что нового для разработчиков?

Как отмечено в этой статье, Windows 10 версии 1909 представляет собой набор функций для повышения производительности, улучшений качества и возможностей компании. Разработчики должны быть в кусе об этой версии, но в настоящее время никаких действий не требуется.

Новый Windows SDK не будет выпущен вместе с этой версией Windows, поскольку в этом релизе не представлены новые API. Это означает, что нет необходимости ориентироваться на Windows 10 версии 1909 или изменять файлы проекта.

Так как обновления SDK пока что нету, вы можете продолжить работу с Windows 10 версии 1903. Это можно сделать самым простым способом, установить Visual Studio 2019.

 

Новое с Windows 10, версия 1903

Центр разработки Windows имеет полный список того, что доступно для разработчиков, которые используют версию 1903. С тех пор мы презентовали Windows UI Library 2.2.

 

WinUI 2.2 выпущен в августе. WinUI имеет открытый исходный код, и каждый может ознакомиться с репозиторием WinUI GitHub, чтобы решить проблемы с файлами, обсудить новые функции и даже внести код. В WinUI 2.2 мы добавили новый элемент управления Tab View. Помимо введения новых обновлений Visual Style, был также апдейт для Navigation View. Мы всем рекомендуем использовать WinUI в своих приложениях UWP - это лучший способ получить новейшую систему проектирования Fluent, контроллы и иметь обратную совместимость с Windows 10 Anniversary Update.

 

2 простых шага для обновления вашей среды разработки

Если вы хотите обновить свою систему Windows 10 до версии 1909, вы можете загрузить ее по подписке VSS, либо воспользоваться WIP (Windows Insider Program) Release Preview Ring. У команды Insider есть отличный пост в блоге, в котором вы узнаете, как попасть в Release Preview Ring. Затем, просто зайдите в Visual Studio 2019 и установите последнюю версию SDK. В последней версии Visual Studio Windows 10 SDK (10.0.18362) уже выбран по умолчанию.

1.      Запустите установщик Visual Studio или перейдите по адресу https://www.visualstudio.com/downloads/ и загрузите его.

2.      Выберите «Universal Windows Platform development» в разделе Workloads, Windows 10 SDK (10.0.18362) будет включен по умолчанию

3.       Нажмите «Install»

 

 

Источник



Posted on 27. June 2019

XAML Islands v1 - Обновления и план выпуска

На конференции Microsoft Build было объявлено, что Windows 10 May 2019 Update (версия 1903) будет включать XAML Islands v1.

Ниже Вы можете ознакомиться с более подробной информацией плана выпуска и двух рабочих направлений в процессе разработки: .NET обертки и поддержка Visual Studio 2019.

Что такое XAML Islands?

XAML Islands позволяют .NET и нативным Win32 приложениям размещать элементы управления UWP XAML. Вы можете расширить возможности и функциональность Ваших существующих десктопных приложений с помощью последних инноваций пользовательского интерфейса, ранее доступных только для UWP приложений. Например, Вы можете использовать такие элементы управления, как ColorPicker, InkCanvas, CalendarView и NavigationView, в Ваших существующих C ++ Win32, Windows Forms и WPF приложениях.

С XAML Islands Вы можете модернизировать Ваше приложение в собственном стиле без необходимости переписывать его заново - просто используйте элементы управления UWP XAML.

Как использовать XAML Islands?

Первый компонент - API  UWP XAML хостинг. Это набор API Windows 10 (Windows Runtime классы и COM интерфейсы), которые поставляются на Windows 10 в версии 1903. Если у Вас есть нативное C ++ Win32 приложение, использующее Common Controls библиотеку (Comctl32.dll) или MFC, и Вам необходимо модернизировать Ваш интерфейс, смело используйте эти API.

Второй компонент состоит из обернутых .NET элементов управления и элементов управления хоста. Это набор оберток API  UWP XAML хостинга в Windows Community Toolkit как для Windows Forms, так и для WPF разработчиков.

Каковы требования для использования XAML Islands в Ваших приложениях?

  • Windows 10 версии 1903 и выше: Независимо от того, есть ли у Вас Win32 или .NET приложение, текущая версия XAML Islands работает только с приложениями для Windows 10 версии 1903 и выше.
  • Windows 10 SDK версия 1903 (10.0.18362): этот SDK содержит заголовки, библиотеки, метаданные и инструменты для сборки Windows 10 приложений, используя XAML Islands.
  • Упакованное десктопное приложение: Десктопные приложения можно упаковать в MSIX для доступа к определенным Windows 10 API, таким как живые тайлы и уведомления. Для упаковки Вашего приложения необходимо использовать Windows Application Packaging Project. Упаковка Вашего приложения не означает, что оно будет работать в изолированной программной среде с ограниченными UWP привилегиями. Вместо этого Ваше упакованное Win32 приложение будет работать в режиме полного доверия. Упакованные с XAML Islands приложения будут оптимизированы для разработчиков, которые работают с Visual Studio 2019.

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

  • Visual Studio 2019: только Visual Studio 2019 будет иметь набор инструментов, необходимый для сборки десктопных приложений с использованием XAML Islands.
  • .NET Core 3.0: эта среда полностью поддерживается для .NET приложений. Некоторые сценарии также будут работать в приложениях, предназначенных для .NET Framework 4.7.2, но для этих приложений существуют некоторые ограничения, например использование управляемых сторонних элементов управления.
Какие .NET Core и .NET Framework версии взаимодействуют с XAML Islands?

Обернутые .NET элементы управления поддерживаются в .NET Framework и .NET Core 3. Хост-элемент .NET (WindowsXamlHost) доступен для Windows Forms и WPF. Этот элемент управления позволяет хостить UWP XAML контент. Если UWP XAML контент - это элемент управления, который поставляется с Windows 10, такой как ColorPicker или NavigationView, Вы можете использовать .NET Framework 4.7.2 или NET Core 3.

Если UWP XAML контент - это пользовательский UWP элемент управления, который внедрен в сторонний WinRT компонент, то .NET версия, на которую Вы можете ориентироваться, зависит от того, каким образом был разработан пользовательский элемент управления. Он будет считаться сторонним WinRT компонентом, если он установлен одним из следующих способов: в отдельном UWP проекте, в Nuget пакете или по ссылке на файл.

  • Если сторонний WinRT компонент является нативным (написан на C ++ / WinRT), пользовательский элемент управления может использоваться как .NET Framework 4.7.2, так и как NET Core 3.
  • Если сторонний WinRT компонент является управляемым (например, написан на C#), пользовательский элемент управления может использоваться только на .NET Core 3. Полная версия .NET Framework не полностью поддерживается в этом сценарии, и она требует некоторой кросс-компиляции для того, чтобы работать на всех.

Таблица поддержки платформы для XAML Islands в1:

Если Ваше приложение нацелено на .NET Core 3, независимо от того, является ли сторонний WinRT компонент, который Вы размещаете, нативным или управляемым, Вы получите оптимизированный опыт работы. Если Ваше приложение нацелено на полную .NET Framework версию, Вы получите оптимизированный опыт, только если Ваш сторонний WinRT компонент является нативным.

Как использовать XAML Islands в Вашем нативном C ++ Win32 приложении?

Если Вы C ++ разработчик, Вам нужно использовать API UWP XAML хостинга. Вот одни из основных шагов:

  • Сначала инициализируется UWP XAML структура в текущем потоке (Вы можете использовать статический InitializeForCurrentThread метод для WindowsXamlManager класса).
  • Создайте DesktopWindowXamlSource объект, который требует HWND от Вашего приложения. DesktopWindowXamlSource создаст ChildWindow, где Вы сможете разместить XAML контент.
  • Вам нужно позаботиться о фокусировке клавиатуры, когда пользователи переходят на XAML Islands и выходят из них. Этот DesktopWindowXamlSource объект предоставляет событие для распределения фокусировки клавиатуры.

Вы можете ознакомиться с более подробной информацией здесь: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/using-the-xaml-hosting-api

Как использовать XAML Islands в Вашем .NET приложении?

Windows Community Toolkit версии 6.0.0 (на данный момент в preview-версии) предоставляет несколько NuGet пакетов для Windows Forms и WPF, которые Вы можете добавить в Ваш проект.

Элемент управления WindowXamlHost является хост-элементом, в котором можно размещать все типы UWP XAML контента.

Обернутые элементы управления создаются для работы с большинством событий и свойств небольшого набора определенных UWP элементов в WPF и Windows Forms элементы. В настоящее время предоставляются следующие упакованные элементы управления:

  • InkCanvas оборачивает UWP InkCanvas и InkToolbar и предоставляет поверхность и панели инструментов для взаимодействия с Ink пользователем.
  • MediaPlayerElement позволяет Вашим .NET приложениям использовать современные аудио- и видео-кодеки и предоставляет более высокую производительность для потоковой передачи и воспроизведения медиа контента.
  • MapControl позволяет использовать в Ваших приложениях последние инновации картографической платформы, например, больше фотореалистичных карт.
  • SwapChainPanel (preview-версия) позволяет добавлять DirectX 12 контент в Ваше приложение.

Какие современные веб элементы доступны для десктопных приложений?

Windows Forms и WPF приложения используют элемент управления WebBrowser, который использует рендеринг Internet Explorer и поэтому не поддерживает HTML5 и другие функции. Windows Community Toolkit содержит Windows Forms и WPF обертки для UWP WebView элемента, который использует Edge в качестве инструмента рендеринга, поэтому эти приложения могут размещать веб-контент, который требуюется на HTML5.

Windows Community Toolkit также содержит элемент управления WebViewCompatible. Этот элемент использует либо WebBrowser, либо WebView рендеринг, в зависимости от Windows версии, на которой работает приложение:

  • Приложения для Windows 10 версии 1803 и выше будут использовать текущий инструмент WebView рендеринга.
  • Приложения для более ранних Windows версий, таких как Windows 8.1, будут использовать более старый инструмент WebBrowser рендеринга.

Используют ли XAML Islands другой поток?

Нет. XAML Islands работают в том же потоке пользовательского интерфейса Вашего десктопного приложения. Вы можете сразу получить доступ ко всем UWP XAML объектам из Вашего кода. Это отличается от Windows Forms и WPF технологий.

Есть ли инструкции?

XAML Islands Lab предоставляет пошаговые инструкции по использованию обернутых элементов управления и хост-элементов в Windows Community Toolkit для добавления UWP элементов управления в уже существующее WPF приложение. Эта работа включает в себя полный код для WPF приложения, а также подробные инструкции для каждого этапа.

Этот C ++ Win32 образец демонстрирует полную реализацию размещения пользовательского UWP элемента управления в неупакованном C ++ Win32 приложении (то есть приложении, которое не встроено в MSIX пакет).

Для WPF .NET Core 3 приложения, которое использует UWP проект в User Control, Вы можете использовать следующее:

Windows Community Toolkit содержит демонстрационные версии для проверки обернутого элемента управления и хост-элемента.

Каков план выпуска XAML Islands в1?

1. Windows 10 May 2019 Update содержит первый выпуск XAML Islands (в1).

2. Windows Community Toolkit, версии 6.0, будет содержать WindowsXamlHost и упакованные элементы управления для .NET Framework.

  • Это запланировано на лето 2019 года.
  • Будет preview-версия 6.1, которая будет содержать WindowsXamlHost .NET Core 3 версию и упакованные элементы управления. Это обновление будет выпущено вместе с .NET Core 3 - во второй половине 2019 года.

3. Visual Studio 2019 обновится во второй половине 2019 года вместе с .NET Core 3, который будет поддерживать XAML Islands в1. Помните, что только с упакованными приложениями Вы получите оптимизированный опыт работы.

Каков план выпуска XAML Islands в2?

XAML Islands в2 будет поставляться в составе WinUI 3.0. Следовательно, вторая версия будет поддерживать те же Windows 10 версии, что и WinUI 3.0. Основной выпуск WinUI v3 запланирован на первую половину 2020 года. WinUI - это проект с открытым исходным кодом, и Вы можете следить за последними событиями здесь: https://github.com/microsoft/microsoft-ui-xaml/blob/master/docs/roadmap.md 

Где оставить отзыв о XAML Islands?