WinRT, внедряване на асинхронни методи

WinRT --- Внедряване на асинхронни методи

По-рано ви показах как да напишете метод със суфикс Async, който извиква един или повече други асинхронни методи. Кодът, предоставен от програмиста за този метод, се изпълнява в нишката на потребителския интерфейс, въпреки че асинхронните методи, извикани от нея, могат да се изпълняват във вторични нишки.

Понякога приложението трябва да извърши много изчислителна работа, която може да парализира нишката на потребителския интерфейс. Ако разделите тази работа на много малки фрагменти, става възможно да използвате DispatcherTimer или събитието CompositionTarget.Rendering, за да я изпълните. Обработчиците на събития се изпълняват в нишката на потребителския интерфейс, но натоварването се разпределя така, че нишката на потребителския интерфейс реагира нормално на въвеждане от потребителя.

Възможно е също да се изпълнява работа във вторична нишка. Едно решение е да се използва класътThreadPool от пространството на имената на Windows.System.Threading, но класът Task е по-общ, така че ще представя това решение тук.

Най-простият метод Task.Run() приема аргумент Action (метод без аргументи и без връщана стойност) и го изпълнява в нишка от пул. Обикновено аргументът се определя от ламбда функция.

Да предположим, че имаме метод (с няколко аргумента), който отнема много време за изпълнение:

Не искате да изпълнявате този метод директно в нишката на потребителския интерфейс, но можете да го поставите в тялото на ламбда функция, предадена на Task.Run(), за да използвате await:

Тъй като Task.Run() изпълнява BigJob() на вторична нишка, методът не може да съдържа код за достъп до обектипотребителски интерфейс. Или по-скоро, ако трябва да съдържа такъв код, тогава трябва да се използва методът RunAsync () на класа CoreDispatcher за това. Ако трябва да се използва оператор за изчакване с това извикване RunAsync(), тогава методът BigJob() трябва да бъде деклариран с ключовата дума async и да върне обект Task). И ето още един метод, който също отнема много време, но връща стойност:

Този метод също би било нежелателно да се изпълнява в нишката на потребителския интерфейс, но може безопасно да се изпълнява чрез метода Task.Run():

Тъй като методът в тялото на ламбда функцията, предадена на Task.Run(), връща двойно (връщаната стойност на CalculateMagicNumber), върнатата стойност на Task.Run() е Task. Операторът await връща двойна стойност, изчислена чрез метода CalculateMagicNumber.

Освен това методът CalculateMagicNumberAsync може да се дефинира по следния начин:

Този метод може да бъде извикан от потребителския интерфейс:

И можете да направите всичко по един метод:

Ако изчислението изисква извикване на други асинхронни методи, тези извиквания трябва да бъдат предшествани от думата await и ламбда функцията трябва да бъде декларирана с ключовата дума async:

Последният формуляр - където всичко се събира в един метод - е най-лесният за внедряване на известия за анулиране и напредък. Много от асинхронните методи, които дефинирате, ще имат някакъв вид цикъл:

Цикли като този са добри както за известия за отмяна, така и за напредък, но бъдете внимателни. Проверката за анулиране или предупреждения не трябва да се извършва хиляда пъти в секунда, нито на всеки пет секунди. Всяка секунда или няколко пъти в секунда е най-подходящата честота. Например, цикли, които се изпълняват хиляди или милиони пъти, могат да включват допълнителна логика, коятопроверява за предупреждения за отмяна или напредък само ако променливата на цикъла се дели равномерно на 100.

За да реализирате анулиране, добавете параметър от типCancellationToken към този метод и извикайте метода ThrowIfCancellationRequested() върху него в удобна точка:

Имайте предвид, че параметърът cancellationToken също се предава във втория аргумент на Task.Run(). Това ви позволява да отмените задачата, преди да започне. Сега, когато извиквате метода CalculateMagicNumberAsync(), трябва да подадете обект CancellationToken като последен аргумент. За да получите този обект, трябва да дефинирате поле за обект от тип CancellationTokenSource:

Този обект трябва да бъде дефиниран като поле, защото трябва да бъде достъпен от метода, който инициира анулирането - най-вероятно по искане на потребителя:

Преди да извикате CalculateMagicNumberAsync(), трябва да създадете нов обект CancellationTokenSource и да предадете неговото свойство Token на метода в блока try:

Ако извикате метода Cancel() на обекта CancellationTokenSource, следващия път, когато извикате метода ThrowIfCancellationRequested на обекта CancellationToken в асинхронния метод, ще бъде хвърлено изключение от тип OperationCanceledException, което се улавя от кода, който извиква асинхронния метод. Други възможни изключения (най-вероятно свързани с I/O файл или достъп до Интернет) се улавят от втория блок catch.

За да може асинхронен метод да издава известия за напредъка на операция, трябва да добавите още един параметър към метода. Този параметър е от типIProgress, където T е типът, който ще се използва за предаване на информация за напредъка. Обикновено T е двойно, но дали ще оцените напредъка по скала от 0 до 1 или от 0 до 100 зависи от вас.себе си. Във втория случай можете да използвате int вместо double. Дори съм виждал пример, в който T е тип bool и true означава край на операцията!

След това на някое удобно място (може би в точката, където операцията се проверява за анулиране), информацията за напредъка се актуализира:

В този код променливата на цикъла се преобразува в двойна; неговите стойности варират от 0 до 100 и представляват процента на завършеност на операцията (което ги прави много удобни за задаване на свойството Value на ProgressBar). В някои случаи в началото на метода се показва отделно съобщение за нулев напредък, а в края - за достигане на максимален напредък.

Ще ви трябва и метод, който приема избрания от вас тип като параметър и актуализира информацията за напредъка:

Този метод се извиква в нишката на потребителския интерфейс. Когато извикате CalculateMagicNumberAsync() (което помните, че е в блок try), се създава обект Progress с метода за обратно извикване, който сте дефинирали като последен аргумент:

Методът за обратно извикване не трябва да бъде под формата на отделна функция, можете да се ограничите до прост ламбда израз:

Нека разгледаме един практически пример.

Следният тестов проект WordFreq чете текстов файл (да речем електронна книга от известния уебсайт на Project Gutenberg) и изчислява броя на повторенията на думите - например, за да можете да разберете колко пъти думата "whale" (кит) се среща в текста на книгата на Херман Мелвил "Моби Дик". Всъщност приложението WordFreq е твърдо кодирано за Moby Dick, въпреки че рутината за броене на думи в метода GetWordFrequenciesAsync() разбира се е универсална.

Методът GetWordFrequenciesAsync() приема аргумент от клас .NET Stream, защото Iискаше да използва обекта .NET StreamReader, за да прочете файла ред по ред. Аргументите CancellationToken и IProgress също се предават на метода.

С връщаната стойност нещата са по-сложни. Методът използва обект .NET Dictionary за съхраняване на броя на срещанията на всяка уникална дума във файла. Съответно ключът на речника е от тип string, а стойността е от тип int. В края на метода функцията LINQ - OrderByDescending() сортира речника по стойност (т.е. думите с най-голямо срещане са първи). Резултатът е колекция от обекти от тип:

Колекцията, действително върната от OrderByDescending(), е обект от общия тип IOrderedEnumerable:

Това означава, че върнатата стойност на метода GetWordFrequenciesAsync() е от тип:

И ето как изглежда методът:

Методът не съдържа обработка на изключения. Ако изключение бъде хвърлено в конструктора на StreamReader или при извикване на ReadLineAsync, то трябва да бъде обработено от кода, който извиква този метод.

XAML файлът на програмата съдържа два бутона за стартиране и отказ (последният първоначално е деактивиран), ProgressBar за проследяване на напредъка, TextBlock за информация за грешка и StackPanel на ScrollViewer за списъка с думи и броячи:

Файлът с кода съдържа метода GetWordFrequenciesAsync(), както и няколко кратки метода за анулиране и известия за напредък:

Единствената част от кода, която все още не сме покрили, е манипулаторът за кликване за бутона "Старт". Предполага се, че се извиква многократно по време на изпълнението на програмата, но не е проектирано да бъде въвеждано повторно (тоест няма да се стартира втори път, докато не приключи предишното изпълнение). По-голямата част от логиката в метода е свързана синициализация на StackPanel, инициализация на ProgressBar, премахване и установяване на блокиране на бутони. Моля, имайте предвид, че всички достъпи до файлове, както и извикването GetWordFrequenciesAsync(), са затворени в блок за опит:

Но тук възниква друг проблем: след като асинхронният метод се върне, програмата трябва да премести данните в StackPanel. Задачата се решава от блока foreach в края на метода. Цикълът изисква тежко взаимодействие с UI обекти (създаване на TextBlock и добавянето му към StackPanel) и просто не може да работи в друга нишка. Дори ако ограничите списъка до думи, които се появяват в Moby Dick поне два пъти (както направих в моята програма), той ще съдържа почти 10 000 елемента.

Такъв цикъл може да парализира потребителския интерфейс, като му попречи да реагира на действията на потребителя и дори да блокира появата на данни на екрана. Решението - макар и не идеално - се основава на използването на следната команда:

Това изчакващо повикване всъщност позволява на друг код да се изпълнява в нишката на потребителския интерфейс и след това се връща, когато този код завърши. По-специално, „другият“ изпълним код може да бъде кодът, внедрен в класа StackPanel, който хоства наследници на TextBlock и обработва персонализирани действия за превъртане на StackPanel в ScrollViewer.

Без това извикване на Task.Yield(), списъкът с думи няма да се появи на екрана за около 5 секунди, след като ProgressBar покаже, че е достигнал максимален напредък. Разбира се, многократните извиквания на Task.Yield() ще отнемат значително повече време за изпълнение на цикъла (както ще видите сами от забавянето, преди бутонът Старт да бъде освободен), но резултатите ще се появят почти веднага. Вие също ще можете да превъртите списъка до неговиязавършите и ще видите, че думата "кит" в "Моби Дик" се среща 963 пъти:

методи

В друго, по-правилно решение, елементът StackPanel изобщо не се използва. Има контроли, предназначени специално за показване на списъци. По-специално, можете да използвате VirtualizingStackPanel, който не създава елементи от списък, докато не влязат в зоната за показване в резултат на превъртане.

С Windows 8, .NET и C# е по-лесно от всякога да използвате асинхронни методи, но все пак трябва да сте внимателни и да тествате задълбочено като разработчик. Например на машината, която използвах, за да напиша статията, изпълнението на метода GetWordFrequenciesAsync() отне до четири секунди. Но когато премахнах проверката за предупреждения за отмяна и напредък, методът се задейства за по-малко от секунда. Не знам за вас, но ми се струва, че при едносекундните асинхронни методи е напълно възможно да се направи без анулиране и известия за напредък.

Цялата тази сложност се дължи и на факта, че в известен смисъл се опитваме да направим невъзможното: да накараме компютрите да вършат много работа, но в същото време да се преструват, че не правят нищо. Приложенията за Windows 8 трябва да изглеждат така, сякаш вдигат тежести без усилие, а това е много тежка работа за програмиста.

За високотехнологичните производства е необходимо да се създаде система от чисти помещения, в които да се контролира замърсяването с микрочастици на околната среда. Оборудването за чисти помещения отчита концентрацията на прах, микроорганизми и частици, за да гарантира успешното производство на високо прецизна електроника.