Posted on 5. February 2023

Відтворення аудіо та відео в додатках .NET MAUI за допомогою нового MediaElement

Відтворення аудіо та відео в додатках .NET MAUI за допомогою нового MediaElement

 

Вийшов один з найбільш очікуваних елементів управління для .NET MAUI – MediaElement. За допомогою MediaElement ви можете легко відтворювати аудіо та відео з вашого додатку .NET MAUI. В цій статті ви дізнаєтеся все, що вам потрібно знати про цю першу версію і про плани на майбутнє! Media Element є частиною .NET MAUI Community Toolkit, бібліотеки, створеної спільнотою, яка підтримується чудовими розробниками з усього світу, як з боку спільноти, так і з боку корпорації Майкрософт.

 

Що таке MediaElement?

З MediaElement ви отримуєте потужний елемент керування, який дозволяє відтворювати мультимедійні дані у вашому додатку .NET MAUI.


Можливо, ви вже знайомі з MediaElement з інструментарію спільноти Xamarin, куди його додали завдяки чудовій роботі учасника спільноти Пітера Фута. Хоча та версія була вже досить непоганою, вона також мала місце для вдосконалення, особливо на Android.


Саме тому, при перенесенні MediaElement на .NET MAUI, ми перебудували все з нуля. Таким чином, ми змогли зберегти всі ті частини, які вже є хорошими, і в той же час покращити ті речі, які потребують доопрацювання.

 

Під капотом

Для Android ми вирішили використовувати ExoPlayer як аналог платформи, замінивши Android MediaPlayer, який ми використовували для Xamarin. Таким чином, ми автоматично отримуємо багато додаткових функцій, які доступні нам з коробки, наприклад, відтворення HTTP-відео в прямому ефірі (HLS), чудові елементи керування транспортуванням платформи та багато іншого.


На iOS та macOS ми використовуємо платформу AVPlayer, як і для MediaElement в Xamarin. Також без змін залишився Tizen-плеєр, який використовує Tizen.Multimedia.Player.


Тепер, коли .NET MAUI будується на основі WinUI, а не UWP, ми використовуємо новий елемент MediaPlayerElement WinUI. Хоча цей елемент керування також є дуже молодим для WinUI, він вже є дуже повним і виглядає багатообіцяючим.

Підтримка різних медіаформатів відрізняється на різних платформах (і, можливо, залежно від того, які кодеки ви встановили), але, використовуючи нативні медіаплеєри платформи, для кожної операційної системи ми використовуємо всю потужність і пов’язану з цим оптимізовану продуктивність.

 

Початок роботи

Почати роботу з MediaElement дуже просто. По-перше, вам потрібно встановити пакет CommunityToolkit.Maui.MediaElement NuGet. Це окремий пакунок від основного пакунка Community Toolkit.


Після завершення встановлення перейдіть до вашого MauiProgram.cs і додайте наступний рядок ініціалізації до MauiAppBuilder:


public static MauiApp CreateMauiApp()

{

    var builder = MauiApp.CreateBuilder();

    builder

        .UseMauiApp<App>()

        // Initialize the .NET MAUI Community Toolkit MediaElement by adding the below line of code

        .UseMauiCommunityToolkitMediaElement()

        // After initializing the .NET MAUI Community Toolkit, optionally add additional fonts, and other things

        .ConfigureFonts(fonts =>

        {

            fonts.AddFont(“OpenSans-Regular.ttf”, “OpenSansRegular”);

            fonts.AddFont(“OpenSans-Semibold.ttf”, “OpenSansSemibold”);

        });


    // Continue initializing your .NET MAUI App here


    return builder.Build();

 

}


Тепер ви готові почати використовувати MediaElement у своєму додатку! Простий приклад у XAML можна знайти нижче.

 

xmlns=“http://schemas.microsoft.com/dotnet/2021/maui”

             xmlns:x=“http://schemas.microsoft.com/winfx/2009/xaml”

             xmlns:toolkit=“http://schemas.microsoft.com/dotnet/2022/maui/toolkit”

             x:Class=“MediaElementDemos.GettingStarted”

             Title=“MediaElement Getting Started”>


     x:Name=“mediaElement”

                      ShouldAutoPlay=“True”

                      ShouldShowPlaybackControls=“True”

                      Source=“https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4”

                      HeightRequest=“300”

                      WidthRequest=“400”

                      … />

 

Це додасть елемент управління MediaElement на сторінку, яка почне автоматично відтворюватися при завантаженні відео, результат запуску на iOS і Windows можна побачити нижче.



У цій статті автор не вдається в подробиці про багаті функції, які вже є в цій першій версії, але важливо зазначити одну річ. Ви, як розробник, несете відповідальність за використання ресурсів MediaElement. Наприклад, додаток може відтворювати відео в режимі “картинка в картинці” або відтворювати аудіо у фоновому режимі, і в цих сценаріях неможливо автоматично визначити, коли потрібно очистити ресурси MediaElement.

Для цього потрібен лише один рядок коду. У фрагменті коду нижче ви можете побачити, як звільняються ресурси, коли користувач переходить зі сторінки ContentPage, на якій відображається елемент управління MediaElement.


public partial class FreeResourcesPage : ContentPage

{

    void ContentPage_Unloaded(object? sender, EventArgs e)

    {

        // Stop and cleanup MediaElement when we navigate away

        mediaElement.Handler?.DisconnectHandler();

    }


}

 

Щоб дізнатися більше про всі поточні функції MediaElement, відвідайте сторінку документації.

 

Source




Posted on 28. January 2023

5 Функцій .NET MAUI для створення відмінних настільних програм

5 Функцій .NET MAUI для створення відмінних настільних програм


Окрім створення кросплатформних програм для мобільних пристроїв за допомогою .NET MAUI, ви також можете створювати чудові настільні програми для Windows та Mac. Можливо, ваша програма зосереджена лише на платформах для настільних комп’ютерів, або, можливо, вона поширюється скрізь на мобільних і настільних формфакторах. Так чи інакше, важливо забезпечити найкращий досвід для своїх користувачів незалежно від того, яким пристроєм вони користуються. Це означає використання всіх переваг апаратного забезпечення та операційної системи, на яких працює ваша програма. У випадку настільного комп’ютера, .NET MAUI надає декілька унікальних функцій для покращення досвіду для користувачів, і сьогодні я збираюся розібрати свій Топ 5 найкращих функцій .NET MAUI.


Мультивікна

Фундаментальною зміною в .NET MAUI стало впровадження Window як основи. Коли ви створюєте та запускаєте свою програму .NET MAUI, вона автоматично має Window за замовчанням, який ваша Application створює та використовує для відображення вмісту. Клас Application має новий метод CreateWindow, який викликається, коли створюється будь-який новий Window. Коли програми працюють на настільному комп’ютері (або планшеті), є більше можливостей для використання, а це означає, що ви можете створити друге або третє Window замість навігації. Розглянемо приклад програми погоди. Коли користувач переходить до міста, ми можемо захотіти відобразити додаткову інформацію, включно з картою. У нас є можливість перейти на цю сторінку або відкрити абсолютно нове вікно за допомогою вбудованого API:


Наші користувачі тепер мають кілька режимів перегляду, якими вони можуть скористатися замість того, щоб обмежуватися одним вікном інформації. У будь-який момент, користувач може закрити вікно, або ми також можемо закрити його програмно.


Щоб отримати додаткові відомості про налаштування підтримки кількох вікон, обов’язково прочитайте документацію щодо мультивікон.


Рядок меню верхнього рівня


Під час використання настільних програм однією з найпоширеніших функцій є панель меню, інтегрована в програму в Windows або в системну панель меню в Mac. За допомогою .NET MAUI ви можете легко інтегрувати панель меню, використовуючи лише декілька рядків коду. Додатковою перевагою є те, що коли ваші користувачі запускають програму на iPad за допомогою клавіатури, вони також зможуть отримати доступ до меню.


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


Кожна ContentPage має колекцію MenuBarItems, яка може мати декілька рівнів меню:

Ви можете створити ці пункти меню безпосередньо в XAML або створити їх програмно в коді, щоб ваші меню були динамічними. Пункти меню можна ввімкнути або вимкнути, мати роздільники, підменю, піктограми в Windows, а крім прив’язки до Command  доступна подія Clicked. Обов’язково перегляньте документацію стосовно рядка меню, щоб дізнатися більше.

Контекстні меню

 

Іноді є бажання надати більше опцій, коли користувач клацає на елемент правою кнопкою миші. Вам потрібне меню, схоже на рядок меню, але на основі певного контексту. Саме тут контекстні меню вступають у гру в програмах .NET MAUI. Вони мають аналогічний API, як і рядок меню, але розміщуються на певному елементі керування. Наприклад, ми можемо захотіти додати коментар до певного міста в нашому додатку погоди. Ми можемо захотіти відкрити нове вікно, а потім надати місце для введення тексту за допомогою редактора.


Ми можемо застосувати MenuFlyout до редактора та заповнити його елементами MenuFlyoutItems, подібними до нашої попередньої панелі меню.

Подібно до панелі меню, ви також можете прив’язуватись до Command  для події, мати піктограми, підменю, розділювачі тощо. Перегляньте документацію щодо контекстного меню, щоб отримати всі деталі.

Підказки

Підказки — це швидкий і простий спосіб збільшити функціональність вашої програми та покращити взаємодію з користувачем. Користувачі комп’ютера мають доступ до миші та клавіатури, а це означає, що ви можете надати додатковий контекст та інформацію, коли вони наводять курсор на елемент керування у вашій програмі. Використання вкладеної властивості TooltipProperties.Text дає змогу вказати додаткову інформацію, яка відображається користувачеві під час наведення курсора миші.

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

Те саме можна зробити програмно в коді для будь-якого елемента керування:

Для отримання додаткової інформації перегляньте документацію, що стосується підказок.

Жести вказівника

Говорячи про вдосконалення настільних програм, коли користувачі переміщаються по екрану, за допомогою миші, .NET MAUI має кілька нових розпізнавачів жестів спеціально для вказівника миші. Ви можете легко побачити, коли будь-який вказівник увійшов, вийшов або перемістився всередині елемента керування.

Тут ми отримуємо події, коли вказівник взаємодіє з нашим Image. Отримавши подію, ми також можемо отримати положення покажчика всередині Image або відносно нього.

І так само у нас є Point, який ми можемо використовувати для виконання дій у нашій програмі. Щоб дізнатися більше про різні засоби розпізнавання жестів, перегляньте документацію.

Ще більше функцій для настільних програм


Ось і все, 5 чудових функцій для покращення ваших програм .NET MAUI на настільних комп’ютерах. Звісно це тільки початок, оскільки є ще багато різноманітних функцій для створення відмінних програм на всіх платформах. Обов’язково перегляньте всю документацію .NET MAUI, щоб знайти інші функції та елементи керування, якими ви можете скористатися.

 

Source

 



Posted on 5. April 2021

GC Perf Infrastructure - Часть 1

Microsoft открывает новый проект GC Perf! Теперь это часть репозитория производительность .Net.

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

Во-вторых, в инфраструктуре много движущихся частей, и, поскольку она все еще находится в стадии разработки, я не удивлюсь, если вы столкнетесь с проблемами при ее использовании. Пожалуйста, будьте терпеливы, когда мы решаем проблемы! У нас нет много ресурсов, поэтому мы не сможем получить к ним доступ сразу. И, конечно, если вы хотите внести свой вклад, для нас это будет очень ценно. Я знаю многих людей, которые читают это, увлечены анализом производительности и проделали огромную работу по созданию / улучшению ее для .NET. А внесение вклада в анализ производительности – это фантастический способ узнать о настройке GC, если вы хотите начать с чего-то. Поэтому я настоятельно рекомендую вам внести свой вклад!

 

Топология

Мы обсудили, хотим ли мы открыть исходный код для этого в своем собственном репо, и пришли к выводу, что мы не будем делать это в основном из-за логистических соображений, поэтому это стало частью репозитория perf в каталоге src/benchmarks/gc (к которому я буду обращаться в качестве корневого каталога). Это не зависит от чего-либо это означает, что вам не нужно ничего создавать вне этого каталога, если вы просто хотите использовать инфракрасную часть GC perf.

Файл readme.md в корневом каталоге описывает общий рабочий процесс и основное использование. Дополнительную документацию можно найти в каталоге документов.

Есть 2 основных компонента инфраструктуры:

Запуск тестов производительности

Это запускает наши собственные тесты перфорирования – это для людей, которым нужно действительно вносить изменения производительности в GC. Это обеспечивает следующие функциональные возможности –

Задание различных аргументов командной строки для генерации разных характеристик perf в тестах, например, разных коэффициентов построения для SOH / LOH и разных коэффициентов пиннинга;

Определение сборок для сравнения;

Задание различных сред, например, различных переменных env для указания конфигураций GC, работы в контейнерах или ситуаций с высокой загрузкой памяти;

Задание различных параметров для сбора трасс с помощью, GCCollectOnly или ThreadTime.

Вы указываете все это в том, что мы называем стендовым файлом (это файл .yaml, но на самом деле это может быть что угодно – мы просто выбрали .yaml). Мы также предоставляем конфигурации для базовых сценариев перфорации, поэтому при внесении изменений их следует запускать, чтобы убедиться, что все не регрессирует.

Вам не нужно запускать тесты – вы можете запускать все что угодно, если только вы можете указать это как программу командной строки, и при этом использовать все остальное, что мы предоставляем, например запуск в контейнере.

Это задокументировано в файле readme, и я буду говорить об этом более подробно в одной из будущих записей в блоге.

Источник для этого находится в exec dir.

Анализ производительности

Это может использоваться без бегущей части вообще. Если вы уже собрали следы перфектов, вы можете использовать их для анализа. Я предполагаю, что больше людей заинтересуются этим, чем бегущей частью, поэтому я посвятим больше контента анализу. В последней публикации GC я уже говорил о том, что вы можете сделать это с помощью Jupyter Notebook (я покажу больше примеров с реальным кодом в следующих записях блога). На этот раз я сосредоточусь на фактической настройке и использовании команд, которые мы предоставляем. Не стесняйтесь попробовать это сейчас

Источник находится в  analysis.

Настройка анализа

После копирования репозитория с производительностью dotnet вы увидите файл readme в директории gc infra root. Настройка подробно описана в этом документе. Если вам просто нужен анализ, вам не нужно выполнять все шаги по настройке. Единственные шаги, которые вам нужны –

Установите Python. 3.7 является минимально необходимой версией и рекомендуемой версией. 3.8 есть проблемы с Jupyter Notebook. Я хотел указать на это, потому что 3.8 – последняя версия релиза на странице python.

Установите необходимые библиотеки python – вы можете сделать это через «py -m pip install -r src / needs.txt», как сказано в readme, и если ошибок не возникает, отлично; но вы можете получить ошибки с pythonnet, который является обязательным для анализа. На самом деле установка pythonnet может быть настолько хлопотной, что мы посвятили ей целый документ. Я надеюсь, что однажды в VSCode будет достаточно хороших библиотек для построения диаграмм на С#, и С# в Jupyter Notebook, будут работать,  поэтому нам больше не понадобится pythonnet.

Создайте библиотеку анализа С#, запустив «dotnet publish» в каталоге src\analysis\managed-lib dir.

Укажите, что анализировать

Допустим, вы собрали трассировку ETW (это может быть из .NET или .NET Core) вам нужно его проанализировать, какой процесс будет для вас интересен (в Linux вы собираете события для интересующего процесса с помощью dotnet-trace, но поскольку инфраструктура работает как в Windows, так и в Linux, это те же шаги). Определение процесса для анализа означает простую запись файла .yaml, который мы называем «файлом статуса теста». Из файла readme для файла состояния теста, который вы пишете только для анализа, нужны только эти 3 строки:

success: true

trace_file_name: x.etl # A relative path. Should generally match the name of this file.

process_id: 1234 # If you don’t know this, use the print-processes command for a list

Вы можете задаться вопросом, зачем вообще указывать строку «success: true» – это просто потому, что нижеприведенная схема также может использоваться для анализа результатов выполнения тестов. Когда вы запускаете множество тестов и анализируете их результаты в области автоматизации, мы ищем эту строку и анализируем только те, которые были успешными.

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

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

C:\perf\src\benchmarks\gc>py . help

Сначала прочтите README.md. Для помощи с отдельной командой используйте py . command-name –help. (Вы можете пропустить —help –hidden чтобы увидеть скрытые аргументы.)

Запустите команду

[пропустить]

команды анализа

Команды для анализа результатов теста (файлы трассировки). Чтобы сравнить небольшое количество конфигурации, используйте diff. Для сравнения используйте chart-configs. Для подробного анализа одной трассы используйте analyze-single или chart-individual-gcs.

analysis-single: с учетом одной трассы, показателей печати и, при необходимости, показателей для отдельных GC.

analyse-single-gc: выводит подробную информацию об одном GC в пределах одной трассы.

[больше вывода опущено и выполнено некоторое форматирование вывода]

 

(Я прошу прощения за форматирование – меня поражает, что у нас, похоже, нет приличной программы редактирования html для ведения блогов, а написание блога в основном состоит из ручного написания html, что очень больно)

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

C: \ perf \ src \ benchmarks \ gc> py. помочь печатным процессам

 

Напечатайте все PID процесса и имена из файла трассировки.

[больше выходных данных пропущено; Я также сделал некоторое форматирование, чтобы избавиться от некоторых столбцов, чтобы строки не были слишком длинными]

 

В качестве примера я специально выбрал тест, который, по моему мнению, не подходит для работы с Server GC, потому что он имеет только один поток, поэтому я ожидаю увидеть некоторый дисбаланс потоков. Я знаю, что дисбаланс возникнет, когда мы отметим объекты старшего поколения, удерживающие объекты молодого поколения, поэтому я буду использовать команду chart-individual-gcs, чтобы показать мне, сколько времени потребовалась каждому потоку.

C:\perf\src\benchmarks\gc>py . chart-individual-gcs C:\traces\fragment\fragment.yaml –x-single-gc-metric Index –y-single-heap-metrics MarkOlderMSec

Это покажет 8 куч. Подумайте о прохождении —show-n-heaps.

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

C:\perf\src\benchmarks\gc>py . chart-individual-gcs C:\traces\fragment\fragment.yaml –x-single-gc-metric Index –y-single-heap-metrics MarkOlderPromotedMB

Это покажет 8 куч. Подумайте о прохождении —show-n-heaps.

Это подтверждает теорию – потому что мы пометили значительно больше одной кучи, что привело к тому, что эта куча потратила значительно больше времени на маркировку.

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

Вот пример .md, который показывает примеры использования некоторых команд. Обратите внимание, что анализ объединения еще не проверен – пиар вышел, и я хотел потратить больше времени на CR, прежде чем объединять его.

Источник



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 17. April 2019

Windows UI Library 2.2

WinUI 2.2 - это последний официальный выпуск Windows UI Library.

Вы можете добавить пакеты WinUI в свое приложение с помощью диспетчера пакетов NuGet: для получения дополнительной информации см. Начало работы с Windows UI Library.

 

WinUI - это проект с открытым исходным кодом, размещенный на GitHub. Мы приветствуем ваши сообщения об ошибках, предложения по улучшению кода в репозитории Windows UI Library.

 

История версий Microsoft.UI.Xaml 2.2

 

Официальный релиз Windows UI Library 2.2

Август 2019

 

Страница выпуска GitHub

Скачать пакет NuGet

 

Новые возможности

 

1)     TabView

Описание:

 

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

 

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

https://docs.microsoft.com/en-us/uwp/api/microsoft.ui.xaml.controls.tabview?view=winui-2.2

 

2)     Обновления NavigationView

 

а) Обновление кнопки “Назад” NavigationView

 

Описание:

В минимальном режиме NavigationView кнопка «Назад» больше не исчезает. При открытии и закрытии панели пользователям больше не нужно перемещать курсор, чтобы нажать кнопку гамбургера. Эта функция будет работать по умолчанию. Вам не нужно вносить какие-либо изменения кода, чтобы сделать эту работу.

б) NavigationView - нет автоматического заполнения

 

Описание:

Разработчики приложений при использовании элемента NavigationView, теперь могут восстанавливать все пиксели в своем окне приложения, и расширить область заголовка.

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

https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/navigationview#top-whitespace

3)   Обновление Visual Style

а) Corner Radius

Описание:

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

GitHub Ссылка:

https://github.com/microsoft/microsoft-ui-xaml/issues/524

б) Border Thickness

 

Описание:

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

GitHub Spec Ссылка:

https://github.com/microsoft/microsoft-ui-xaml/issues/835

в) кнопка Visual

Описание:

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

GitHub Spec Ссылка:

https://github.com/microsoft/microsoft-ui-xaml/issues/953

г) SplitButton

Описание:

Визуальное отображение SplitButton по умолчанию было обновлено, чтобы сделать его более отчетливым от DropDownButton.

GitHub Spec Ссылка:

https://github.com/microsoft/microsoft-ui-xaml/issues/986

д) ToggleSwitch

Описание:

Ширина ToggleSwitch по умолчанию была уменьшена с 44 до 40 пикселей, поэтому он сбалансирован визуально, сохраняя удобство использования.

GitHub Spec Ссылка:

https://github.com/microsoft/microsoft-ui-xaml/issues/836

 

е) CheckBox и RadioButton

Описание:

Визуальные элементы CheckBox и RadioButton были обновлены, чтобы соответствовать остальной части изменений визуального стиля.

https://github.com/microsoft/microsoft-ui-xaml/issues/839

 

Предварительный релиз Microsoft.UI.Xaml 2.2.190702001

Июль 2019

Страница релиза GitHub

Скачать NuGetпакет загрузки

Экспериментальная особенность

·         TabView

Пререлиз Microsoft.UI.Xaml 2.2.20190416001

Апрель 2019

Страница релиза GitHub

Скачать NuGet пакет загрузки

Экспериментальные особенности

·         FlowLayout

·         LayoutPanel

·         RadioButtons

·         ScrollViewer

 

Источник



Posted on 12. March 2019

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

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

О примере

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

 

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

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

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

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

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

 

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

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

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

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

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

Если система выдает ошибки, то нужно внести дополнительные корректировки. Здесь предоставлена информация о внесении изменений в код с возможными исправлениями для каждого вопроса. Шаги ниже также помогут лучше понять процесс переноса, поэтому, если быстрый способ помог, но Вам интересно узнать «почему и как», продолжайте читать.
  • Перейдите в.csproj файл в SDK-style. Чтобы переместить приложение в .NET Core, сначала нужно изменить файл проекта в SDK формате, поскольку старый формат не поддерживает .NET Core. Кроме того, SDK-style формат намного проще и с ним легче работать. Убедитесь, что копия текущего .csproj файла создана. Замените содержимое .csproj файла следующим. Для WinForms приложения:
  

  
    WinExe
    net472
    true
    false
  
Для WPF приложения:

  
    
  
    WinExe
    net472
    true
    false
  
Обратите внимание, что значение было установлено как false. В проектах нового стиля AssemblyInfo.cs генерируется автоматически по умолчанию. Так что, если проект уже содержит файл AssemblyInfo.cs файл (а он содержит), то необходимо отключить авто-генерацию или удалить файл.

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

Ссылка на NuGet пакет
 

Ссылка на проект
 


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

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




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

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

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

dotnet new wpf -o \MatchingGame.Core\
После того, как был создан новый WinForms .NET Core проект, его необходимо добавить в Ваше решение.
  • Свяжите проекты. Сначала удалите все файлы из нового проекта (прямо сейчас он содержит общий Hello World код). Затем свяжите все файлы из существующего .NET Framework UI проекта с .NET Core 3.0 UI проектом, добавив в .csprojfile файл следующую команду.
    

    
    

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

  

  
    MSBuild:Compile





  
    MSBuild:Compile
  
  

  • Совместите пространство имен по умолчанию и имя сборки. Поскольку Вы ссылаетесь на файлы, созданные дизайнером (например, Resources.Designer.cs), то нужно убедиться, что версия Вашего .NET Core приложения использует то же пространство имен и то же имя сборки. Скопируйте следующие параметры из Вашего .NET Framework проекта:

    
    

  • Отключите AssemblyInfo.cs файл. Как было отмечено ранее, в проектах нового стиля AssemblyInfo.cs генерируется автоматически по умолчанию. В то же время AssemblyInfo.cs файл из старого WinForms проекта будет скопирован и в новый проект, так как все ** \ *.cs файлы были связаны на предыдущем шаге. Это приведет к дублированию AssemblyInfo.cs. Чтобы избежать этого в файле MatchingGame.Core проекта, для GenerateAssemblyInfo было установлено значение false.
false
  • Запустите новый проект. Установите новый .NET Core проект в качестве StartUp проекта и запустите его. Убедитесь, что все работает.
  • Скопируйте или оставьте ссылку. Теперь вместо связывания файлов можно скопировать их из старого .NET Framework UI проекта в новый .NET Core 3.0 UI проект. После этого предыдущий можно удалить.
Использование WinForms Designer для проектов .NET Core

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

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

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



Posted on 2. February 2019

Windows Template Studio - Версия 3.0!

Microsoft очень рады объявить о выпуске Windows Template Studio 3.0!

Ваши отзывы и пожаления очень важны для сообщества. Если Вы заинтересованы, пожалуйста, перейдите на страницу WTS на Github.

Что нового:

Полный список изменений в версии 3.0 Вы можете просмотреть на странице WTS на Github.

Включено в эту версию:

  • Код теперь генерируется как многопроектное решение. Это позволит лучше повторно использовать и разделять логику кода. В результате это будут UWP и .NET Core проекты.
  • Добавление новых проектов по щелчку правой кнопкой мыши.
  • Горизонтальная навигация просмотра заменена шаблоном навигации Pivot.
  • Обновление MVVMLight для использования библиотеки .NET Standard.
  • Общие исправления ошибок.

Обновления платформы разработки:

  • AdaptiveCards к версии 1.1.2
  • AppCenter.Analytics к версии 1.12.0
  • AppCenter.Crashes к версии 1.12.0
  • Services.Store.Engagement к версии 10.1810.16002
  • UI.Xaml to v2.0.181018003.1
  • Json к версии 12.0.1
  • UI.for.UniversalWindowsPlatform к версии 1.0.1.3
Известная проблема
  • В предварительном просмотре Visual Studio 2019 с поддержкой для нескольких проектов произошел сбой NuGet ссылки для Core проекта. (#2862). Чтобы обойти эту проблему, Вам нужно добавить ссылку вручную. Единственная важная ссылка - это Newtonsoft.Json в Core.
  • Форма отправки обратной связи была удалена, пока ошибка не будет исправлена в SDK (#2879).
Как получить обновление:

Есть две возможности обновления к новой сборке.
  • Уже установлено: Visual Studio автоматически обновляет расширение. Для принудительного обновления, откройте «Инструменты» --> «Расширения и обновления». Затем перейдите на вкладку слева «Обновление расширителя», там Вы увидите «Windows Template Studio», после чего нажмите «Обновить».
  • Не установлено: Перейдите на https://aka.ms/wtsinstall, нажмите «загрузить» и дважды щелкните по VSIX установщику.
Что будет в следующих версиях?

Microsoft ценит Ваше участие и поддержку в сообществе. Кроме того, на данный момент ведется активная работа над новыми функциями, которые будут добавлены в будущих обновлениях. Вот некоторые из них:
  • Шаблон навигации в стиле Menubar (версия 3.1)
  • Идентификационный логин (версия 3.1)
  • Улучшения поддержки для Visual Studio 2019
  • Добавление Azure функций (версия 3.1 и выше)
  • Группа тестовых проектов
В партнерстве с сообществом, Microsoft продолжит работу над добавлением и улучшением функций и функциональности. Команда Windows Template Studio всегда рада Вашим отзывам, и если Вам интересно, пожалуйста, перейдите на GitHub --> https://aka.ms/wts. Если у Вас есть идеи по добавлению новых функций, пожалуйста, отправьте Ваш запрос!



Posted on 28. November 2018

Встраивание ввода данных в UWP приложения

Одной из наименее известных возможностей UWP приложений является встраивание ввода данных. Это будет особенно полезно, если Вы хотите предоставить пользователям ознакомительную инструкцию, мгновенную обратную связь с помощью дополнительных технологий или реализовать вкладку Help в приложении. Рассмотрим подробнее преимущества пространства имен Windows.UI.Input.Preview.Injection и его внедрение в приложения.

Поддерживаемые типы ввода

Windows 10 поддерживает несколько типов ввода. В настоящее время это:

  • Геймпад
  • Клавиатура
  • Мышка
  • Стилус
  • Сенсор

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

Возможность внедрения ввода

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

Добавление этой возможности простое и понятное. Откройте Package.appxmanifest как XML-файл и добавьте следующее описание пространства имен в Package элемент:

 


 

 

Пространство имен rescap - это хранилище для ограниченных возможностей. Теперь Вы можете добавить функцию inputInjectionBrokered в разделе Capabilities:


  
       
       


Основы

Основным классом в Windows.UI.Input.Preview.Injection является InputInjector. Вы можете создать его образец, вызвав статический TryCreate метод. После чего Вы можете вызвать соответствующие методы для каждого типа ввода:

 

var inputInfo = new InjectedInputMouseInfo();
...
InputInjector inputInjector = InputInjector.TryCreate();
inputInjector.InjectMouseInput(new[] { inputInfo });

 

 

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

Ввод с мышки

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

 

var info = new InjectedInputMouseInfo();
info.MouseOptions = InjectedInputMouseOptions.Move;
info.DeltaY = 100; //move down 100 points

 

Свойство MouseOptions позволяет указывать места, которые влияют на выполнение действий. Среди них LeftDown и LeftUp, которые можно использовать для имитации простого щелчка мышки:

 

var down = new InjectedInputMouseInfo();
down.MouseOptions = InjectedInputMouseOptions.LeftDown;

var up = new InjectedInputMouseInfo();
up.MouseOptions = InjectedInputMouseOptions.LeftUp;

InputInjector inputInjector = InputInjector.TryCreate();
inputInjector.InjectMouseInput(new[] { down, up });

 

Вы также можете имитировать как вертикальный, так и горизонтальный скрол мышки, используя Wheel и HWheel. Вы можете указывать расстояние прокрутки, используя свойство MouseData:

 

var info = new InjectedInputMouseInfo();
info.MouseOptions = InjectedInputMouseOptions.Wheel;
info.MouseData = 500; //scroll up

InputInjector inputInjector = InputInjector.TryCreate();
inputInjector.InjectMouseInput(new[] { info });

 

Прокрутка - это легко. Но как насчет прокрутки вниз?

 

info.MouseData = -500; //compile time error!

 

Обратите внимание, что свойство MouseData - это uint единица, которой нельзя присвоить отрицательные значения.

 

unchecked
{
    info.MouseData = (uint)-500; //scroll down
}

 

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

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

Ввод с клавиатуры

Класс InjectedInputKeyboardInfo является базой для внедрения ввода с клавиатуры. Наиболее важное свойство - VirtualKey, которое указывает, к какому ключу относится ввод. Используя KeyOptions, Вы можете указывать дополнительные опции, такие как симуляция key up события.

Следующий образец показывает строку «привет» в активном поле ввода:

 

InputInjector inputInjector = InputInjector.TryCreate();
foreach (var letter in "hello")
{
    var info = new InjectedInputKeyboardInfo();
    info.VirtualKey = (ushort)((VirtualKey)Enum.Parse(typeof(VirtualKey),
                                 letter.ToString(), true));
    inputInjector.InjectKeyboardInput(new[] { info });
    await Task.Delay(100);
}

 

В конце foreach узла находится Task.Delay вызов. Это гарантирует, что повторные нажатия клавиш не регистрируются как одно нажатие.

Образец кода

Исходный образец кода доступен на GitHub.

Application Sample

В заключении

В этой статье мы подробно изучили пространство имен Windows.UI.Input.Preview.Injection и посмотрели, как его можно использовать для имитации ввода с разных типов устройств. Не смотря на то, что эта опция не так часто используется, она все же полезна в UWP API.

Дополнительная нформации



Posted on 6. October 2018

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

Один из часто задаваемых вопросов: «Как привлечь клиентов и увеличить количество оценок и отзывов в приложении?»

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

6 советов, которые Вам помогут:

Просите отзывы

Будьте настойчивыми! Спрашивайте пользователей об их мнение и опыте использования. 

1) Спрашивайте в приложении

Добавьте в Ваше приложение окно оценки и голосования или же напрямую запускайте страницу оценок и отзывов в Microsoft Store. Вы также можете использовать Feedback Hub для отзывов Ваших пользователей.

2) Спрашивайте с помощью push-уведомлений

 

Отправляйте toast- или push-уведомления с просьбой оставить оценку и отзыв. Чтобы привлекать только тех пользователей, которые еще не оценили Ваше приложение, сначала создайте сегмент с определением «Has rated == false», а затем настройте push-уведомления.


 

3) Спрашивайте в Microsoft Store

Добавьте просьбу оставить оценку и отзыв в разделе «Что нового в этой версии» или в описании Вашего продукта в Microsoft Store. После того, как Вы расскажете об обновлениях, сообщите пользователям, что Вы ждете их отзывов. К примеру: «Пожалуйста, помогите улучшить приложение, оставьте Вашу оценку и отзыв».

Отвечайте пользователям

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

4) Отвечайте на отзывы в Store 

Пользователи должны знать, что их мнение важно. Вы можете отвечать на их отзывы через API или с панели инструментов. Подробнее об этом здесь

5) Отвечайте прямо на отзыв

Хотите отправлять конкретные комментарии? Используйте Feedback Hub в Вашем приложении. Вы можете отвечать публично или приватно и уведомлять о состоянии любой жалобы пользователя, над которой Вы работаете.

Ваши ответы должны быть краткие (не больше 1000 символов), вежливые и точные. Подробнее об ответах на отзывы читайте здесь.

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

6) Работайте над улучшениями

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

Эти советы помогут Вам сообщить пользователям о важности их мнения!



Posted on 26. April 2018

Windows Template Studio 2.0

Windows Template Studio обновлен к новой версии 2.0. Команда Microsoft с нетерпением ждет, когда Вы начнете создавать замечательные приложения, используя новую версию. Template Studio - это проект с открытым исходным кодом, главная цель которого - ускорить создание приложений с использованием передовых методов и надежной исходной базой кода.

Microsoft благодарны всем за участие в проекте, основанном на сообществах, огромным вкладам / партнерским отношениям, которые были осуществлены в течение прошедшего года с начала выпуска проекта.

Как получить обновление?

Есть две возможности обновления к новейшей сборке.

Уже установлено: Visual Studio автоматически обновляет расширение. Для принудительного обновления, откройте «Инструменты» -> «Расширения и обновления». Затем перейдите на вкладку слева «Обновление расширителя», и Вы увидите Windows Template Studio, после чего нажмите «Обновить».

Не установлено: Перейдите на https://aka.ms/wtsinstall, нажмите «загрузить» и дважды щелкните по VSIX установщику.

Что нового?

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

К тому же, после того, как будет выпущено следующее Windows 10 обновление, будет добавлен навигационный контроль (NavigationView) для платформы UWP Community Toolkit с потрясающим гамбургер меню контролем, который обеспечит удобный переход между классической и контрольной платформами. На данный момент Microsoft работает над документацией, чтобы упростить переход между существующим контролем и новой платформой.

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

Microsoft активно улучшает документацию. В будущем большинство документаций будут перенесены на docs.microsoft.com, которые охватят шаблоны и будут использовать актуальный wizard. Документация, которая останется на WTS GitHub, будет относиться к документации по модификации движка или созданию шаблонов для использования wizard. 

Будущее развитие, обновление и взаимодействие с сообществом

Двигаясь вперед, Microsoft продолжит улучшать и добавлять новые функции и шаблоны.
Большинство самых значимых пожеланий, замечаний об ошибках и лучших идей было предоставлено вами, участниками сообщества. Команда WTS чрезвычайно рада тому, что была частью этого проекта и благодарна всем, кто подал заявку и проявил участие. Если у Вас есть ошибки или идеи по добавлению новых функций, пожалуйста, отправьте Ваш запрос на GitHub --> https://aka.ms/wts 

Анонс в блоге команды Майкрософт