Программная логика приложений для Windows 8 и их взаимодействие с системой

Ключевые концепции компонентов WinRT

Разбить на страницы
Показывать лекцию целиком

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

Структура компонента

  • Выходные файлы компонентов на C#/VB –.NET-сборка с метаданными Windows представлены в виде файла .winmd; у C++ компонентов это DLL-файл с кодом и .winmd-файл с метаданными.
  • Приложения, которые используют компоненты, должны включать файлы.winmd/DLL в состав проектов, на них должны быть добавлены ссылки. Включать в состав проектов исходный код компонентов необязательно.
  • Классы компонентов, которые могут быть использованы из других языков, называются активирумыми (activatable) классами. В C# они должны быть маркированы как public sealed, в Visual Basic как Public NotInheritable, и в C++ как public ref sealed. Компонент должен иметь хотя бы один активируемый класс для того, чтобы им можно было пользоваться из других языков.
  • Классы могут иметь статические (static) члены (свойства и методы), которые можно использовать без создания экземпляра объекта этого класса.
  • Компоненты могут содержать множество общедоступных активируемых классов, так же как и дополнительных, внутренних классов. Все общедоступные классы должны располагаться в одном и том же корневом пространстве имен, которое имеет то же имя, что и файл метаданных компонента.
  • По умолчанию, все общедоступные классы в компоненты видимы из других языков. Класс может быть скрыт от JavaScript путем применения атрибута ) (то есть, перед объявлением класса должно идти [Windows.Foundation.Metadata.WebHostHidden] в C# или [Windows::Foundation::Metadata::WebHostHidden] в C++. Это применимо к классам, которые работают с пользовательским интерфейсом (подобная функциональность не может быть использована совместно с JavaScript, как, например, все пространство имен Windows.Xaml в WinRT) или к классам, функциональность которых совпадает со встроенными возможностями JavaScript (такими, как Windows.Data.Json).
  • Для того, чтобы узнать некоторые дополнительные сведения о структуре компонентов, смотрите следующие примеры из Windows SDK (все они используют WRL, смотрите врезку "Библиотека шаблонов C++ среды выполнения Windows (WRL)"):
  • "Создание внутрипроцессного WinRT-компонента (C++/CX)" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-460a535f)
  • "Создание внутрипроцессного WinRT-компонента (C#)" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-367fada2)
  • "Создание EXE WinRT-компонента на C++" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-ed84af9d)
  • "Создание DLL WinRT-компонента на C++" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-6c399797)
  • Типы

  • Внутри компонента вы можете использовать собственные типы данных языка (то есть, .NET-типы и типы C++). На уровне интерфейса компонента (Application Binary Interface, или ABI), вы должны использовать WinRT-типы или собственные типы языка, которые совместимы с WinRT-типами. В противном случае их значения не смогут быть спроецированы в другие языки. В C++, типы WinRT существуют в пространстве имен ), посмотрите так же материал "Система типов (C++/CX)" (http://msdn.microsoft.com/library/windows/apps/hh755822.aspx). В C#/VB, они существуют в пространстве имен System, посмотрите материал "Сопоставление типов .NET Framework с типами среды выполнения Windows" (http://msdn.microsoft.com/library/windows/apps/hh995050.aspx)
  • Компоненты могут использовать структуры, созданные с помощью типов WinRT, которые проецируются в JavaScript как объекты со свойствами, соответствующими членам объекта struct.
  • Коллекции должны использовать специальные типы WinRT, которые находятся в), такие, как IVector, IMap (и IMapView), и IPropertySet. Именно поэтому нам так часто встречаются векторы.
  • К массивам применимы специальные условия, так как их можно отправлять лишь в одном направлении, как мы видели в кратких руководствах. Таким, образом, каждый из них может быть маркирован либо как массив только для чтения, либо как массив только для записи. Смотрите материал "Передача массивов в компонент среды выполнения Windows" (http://msdn.microsoft.com/library/windows/apps/hh975353.aspx). У массивов так же есть ограничение на использование с асинхронными методами, так как выходной массив не возвращается вызывающему объекту при завершении асинхронной операции. Больше об этом мы поговорим в разделе "Реализация асинхронных методов" ниже.
  • Реализация компонентов

  • Создавая перегруженные методы, убедитесь, что арность (arity) (количество аргументов) у каждой из них различается, так как JavaScript не может определять перегруженные методы лишь по типу. Если вы создаете несколько перегрузок с тем же количеством аргументов, одна из них должна быть отмечена атрибутом ) для того, чтобы при использовании JavaScript-проекции было известно, какой из них нужно использовать.
  • Делегат (анонимная функция в определении JavaScript), это объект функции. Делегаты используются для событий, обратных вызовов и асинхронных методов. Объявление делегата ведет к определению сигнатуры функции.
  • Ключевым словом event отмечается общедоступный член специального типа делегата в виде событие. Делегаты события – сигнатура для обработчика – могут иметь тип (то есть, EventHandler<T>), что означает, что объект ).
  • Выдача исключения: используя ключевое слово throw в C#, VB, и C++ вы вызовете новый экземпляр объекта с типом исключения в пространстве имен System (http://msdn.microsoft.com/library/windows/apps/hh454070.aspx). В C++ используйте throw ref new с одним из типов исключений в пространстве имен Platform namespace, например, Platform::InvalidArgumentException (http://msdn.microsoft.com/library/windows/apps/hh755794.aspx). Это отобразится в JavaScript со сведениями о стеке, в поле сообщения исключения. Действительное сообщение от компонента появлится в диалоговом окне исключений Visual Studio
  • Реализация асинхронных методов

    Какими бы быстрыми не были процедуры, выполняемые на C# и C++, которые мы видели в разделе "Быстрый старт", факт заключается в том, что их исполнение может занимать более 50 мс при выполнении в потоке пользовательского интерфейса. Это – рекомендованный порог, достигнув которого, вы должны задуматься о том, чтобы сделать операцию асинхронной. Это означает исполнение кода в другом потоке, в итоге, пользовательский интерфейс не блокируется вовсе. Для того, чтобы показать основы, следующие разделы показывают, как реализовать асинхронную версию простой функции countFromZero , которую мы видели ранее, во врезке, где сравнивался управляемый и машинный код. Сначала мы реализуем это с помощью рабочего процесса, а потом – в C# и C++.

    Для C#/VB и C++ имеется подробная документация по созданию асинхронных методов. В базовом материале, который мы уже упоминали, "Создание компонентов среды выполнения Windows в C# и Visual Basic" (http://msdn.microsoft.com/library/windows/apps/br230301.aspx) есть подраздел "Асинхронные операции", посвященный этому. В материале "Пошаговое руководство. Создание основного компонента среды выполнения Windows в C++ и его вызов из кода JavaScript" (http://msdn.microsoft.com/library/windows/apps/hh755833.aspx) есть подраздел "Добавление в класс асинхронных открытых методов". Есть материал "Создание асинхронных операций в C++ для приложений для Магазина Windows" (http://msdn.microsoft.com/library/hh750082.aspx). Кроме того, имеется серия сообщений в Блоге для разработчиков приложений для Windows 8, которые посвящены как приложениям, так и компонентам. В частности, это следующие: "Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/03/28/windows.aspx), "Подробное рассмотрение WinRT и await" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/05/04/winrt-await.aspx), "Предоставление задач .NET в виде асинхронных операций WinRT" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/06/25/net-winrt.aspx). Попытка сравниться в глубине изложения с этими материалами была бы сопряжена с массой повторений, поэтому следующий раздел сосредоточен на создании асинхронной версии методов, работающих с пиксельными данными из кратких руководств и рассмотрению уроков, которые мы можем извлечь из опыта работы над ними.

    Конкретный сценарий, с которым мы работаем – проводим преобразование пиксельных данных в оттенки серого и отправляем результат в элемент canvas – позволит выявить ряд сложностей, с которыми полезно будет поработать, и которые не упомянуты напрямую в другой документации. Это включает в себя сложности с передачей массивов между приложениями и компонентами, что представляет интересный шаблон проектирования, которые используется некоторыми API WinRT. Тем не менее, решение приводит нас к чему-то вроде тупика, из-за ограничений элемента HTML canvas. Это заставляет нас задуматься об альтернативах, что является хорошим упражнением, так как вы, вероятно, столкнетесь с другими сложностями в вашей собственной работе над компонентами.

    Рабочие процессы JavaScript

    При работе исключительно с JavaScript, рабочие процессы – это тот способ, с помощью которого можно передать исполнение кода в другие потоки. Главное, что нужно здесь понять – это то, что взаимодействие между главным приложением (потоком пользовательского интерфейса) и рабочими процессами происходит посредством отдельных сообщений postMessage и с посощью связанного с ними события message. Таким образом, рабочие процессы не похожи на компоненты, в которых вы можете просто вызывать методы и получать результат их выполнения. Если вы хотите вызвать метод с конкретными аргументами внутри рабочего процесса, вы должны выполнять подобный вызов с помощью сообщения postMessage, которое содержит необходимые значения. С другой стороны, функция, которая вызвана внутри рабочего процесса, возвращает результат в главное приложение, так же вызывая postMessage.

    Например, в упражнении "Image Manipulation", я поместил функцию countFromZero в js/worker_count.js вместе с обработчиком сообщения, который служит как простой диспетчер метода:

    onmessage = function (e) {
    switch (e.data.method) {
    :
    case "countFromZero"
    countFromZero(e.data.max, e.data.increment);
    break;
    
    default:
    break;
    }
    };
    
    function countFromZero(max, increment) {
    var sum = 0;
    max = 10;
    
    for (var x = 0; x < max; x += increment) {
    sum += x;
    }
    
    postMessage({ method: "countFromZero", sum: sum });
    }

    Когда запускается этот рабочий процесс, единственный код, который исполняется, это назначение onmessage. Когда обработчик получает подходящее сообщение, он запускает функцию countFromZero, которая в ответ отправляет результаты своей работы. Другими словами, настройка рабочего процесса означает конверсию вызовов методов и результатов в сообщения.

    Вызов данного метода из приложения теперь выглядит так:

    var worker = new Worker('worker_count.js');
    worker.onmessage = function (e) { //e.data.sum это результат }
    
    //Вызов метода
    worker.postMessage({ method: "countFromZero", max: 1000, increment: .00005 });

    Имейте в виду, что сообщения postMessage – это асинхронные операции, таким образом, нет конкретных гарантий того, как быстро эти сообщения будут доставлены рабочему процессу или приложению. Более того, когда рабочий процесс создан, он не может приступить к работе до тех пор, пока исполняется скрипт (как когда вы вызываете setImmediate). Это означает, что рабочие процессы не особенно хорошо подходят для асинхронных операций, которые вы хотите запустить так быстро, как только возможно и от которых вы хотите получить результат, как только он будет готов. По этой причине, рабочие процессы лучше подходят для сравнительно больших операций и выполнения текущей работы. Маленькие, с высокой скоростью отклика, высокопроизводительные подпрограммы лучше размещать в WinRT-компонентах.

    Механизм postMessage так же не лучшим образом подходит для объединения нескольких асинхронных операций в цепочку, что мы легко можем реализовать с помощьюpromise-объектов, которые поступают из API WinRT. Честно говоря, я не хочу даже начинать думать о подобном коде! Я предпочитаю вместо этого задаваться вопросом о том, есть ли способ, с помощью которого мы можем эффективно заключить механизм сообщений рабочего процесса в оболочку promise-объекта, таким образом мы сможем обращаться с асинхронными операциями одинаково, несмотря на особенности их реализации.

    Чтобы это сделать, нам нужно найти способ получения результата из обработчика worker.onmessage и передачи его в обработчик завершения promise-объекта. Для того, чтобы это реализовать, мы используем некоторый код в главном приложении, который, на самом деле, выполняет ту же функцию, что и уровень проекции JavaScript, используемый для превращения WinRT API в promise-объекты

    // Это переменная функции, которую мы подключаем. 
    var workerCompleteDispatcher = null;
    
    var promiseJS = new WinJS.Promise(function (completeDispatcher, errorDispatcher,
    progressDispatcher) {	
    workerCompleteDispatcher = completeDispatcher;	
    });	
    
    // Здесь рабочий процесс должен быть создан и сохранен в переменной 'worker' variable
    // Прослушивание событий рабочего процесса
    worker.onmessage = function (e) {	
    if (workerCompleteDispatcher != null) {	
    workerCompleteDispatcher(e.data.sum);
    }	
    }	
    promiseJS.done(function (sum) {
    // Вывод данных рабочего процесса JavaScript 
    });

    Первое, что здесь нужно понять, это то, что, на самом деле, выполняет promise-объект, и как promise-объект отделен от асинхронной операции. (Так и должно быть, так как API и компоненты WinRT ничего не знают о promise-объектах). На самом деле, promise-объект – это лишь инструмент для управления множеством функций-прослушивателей от имени асинхронной операции, такой, как наш рабочий процесс. То есть, когда асинхронная операция обнаруживает определенные события, а именно, события завершения, ошибки, прогресса операции, она хочет сообщить об этом тому, кто выразил интерес к этим событиям. Этот кто-то делает это путем вызова методов then или done promise-объектов и предоставляя один или большее количество обработчиков.

    В методах then или done, все, что реально делают promise-объекты, это сохраняют указанные функции в списке (если только они не знают, что асинхронная операция уже завершана, в подобном случае они могут просто вызывать функцию обработки завершения или ошибки немедленном). Именно поэтому вы можете вызвать then или done много раз для того же самого promise-объекта – такие вызовы просто добавляют ваши обработчики завершения, ошибки и прогресса операции в соответствующий список внутри promise-объекта. Конечно, эти списки бесполезны, если нет какого-то способа вызвать обработчики, которые в них хранятся. Для этой цели у promise-объекта есть три собственных функции, которые просматривают каждый список и вызывают зарегистрированные прослушиватели. На самом деле, это – основная цель promise-объекта: управлять списками прослушивателей и вызывать эти прослушиватели тогда, когда будет нужно.

    Код, который запускает асинхронную операцию, таким образом, будет использовать promise-объект для управления этими прослушивателями, отсюда и вызов new WinJS.Promise. Но так же нужно получить доступ к этим функциям в promise-объекте, которые следует вызвать для уведомления их прослушивателей. Это – цель анонимной функции, предоставленной конструктору promise-объекта. Когда promise-объект инициализирован, эти функции вызываются со ссылками на функции, которые оповещают прослушиватели. Код асинхронной операции затем сохраняет все, что нужно, для более позднего использования. В нашем случае с рабочим процессом, мы заинтересованы лишь в оповещении обработчика завершения, поэтому мы сохраняем ссылку на подходящую функцию в переменной workerCompleteDispatcher.

    Когда затем мы обнаруживаем, что операция завершена – когда мы получаем соответствующее сообщение от рабочего процесса – мы проверяем, подходящая ли ссылка записана в workerCompleteDispatcher и затем вызываем ее с результирующим значением. Диспетчер, снова, перебирает все зарегистрированные прослушиватели и вызывает их с тем же результатом. В вышеприведенном коде единственный подобный прослушиватель – это анонимная функция, которую мы передали в promiseJS.done.

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

    Асинхронная операция (Async Operation)

    Promise-объект (Promise)

    Инициализация (initialize)

    Регистрация прослушивателей (register listeners)

    Приложение (Клиент) (App (Client))

    (рис 14.1) Promise-объект управляет прослушивателями и вызывает их от имени асинхронной операции

    Снова, все, что вы видите здесь, за исключением вызова done (который является клиентским кодом, а не частью асинхронной операции), это то, что делает уровень проекции JavaScript для асинхронных операций, поступающих из WinRT. В этих случаях асинхронная операция представлена объектом с интерфейсом IAsync*, а не рабочим процессом. Вместо прослушивания события message рабочего процесса, уровень проекции просто подключает себя через интерфейс IAsync* и создает promise-объект для управления соединениями из приложения.

    Вышеприведенный код включен в упражнение "Image Manipulation" в дополнительных материалах к лекции. Полезно в учебных целях установить точки останова на все анонимные функции и пошагово исполнить код для того, чтобы точно увидеть, где они вызываются, даже для того, чтобы пройти в WinJS для того, чтобы увидеть, как все это работает. В конце концов, очень важно то, что этот код предоставляет нам promise-объект (в promiseJS), который выглядит и работает точно так же как любой другой promise-объект. Это становится очень удобным, когда у нас есть promise-объекты от других асинхронных операций, как описано позже, во врезке "Объединение promise-объектов". Это означает, что мы можем смешивать и сочетать асинхронные операции из API WinRT, из компонентов WinRT и рабочих процессов.

    Основы асинхронности в WinRT-компонентах

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

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

    Во-вторых, возвращаемое значение метода должно быть одним из следующих интерфейсов (http://msdn.microsoft.com/library/windows/apps/br211924.aspx), показанных в таблице ниже, каждый из которых представляет определенноую комбинацию из асинхронных способов поведения, а именно, того, предоставляет ли метод результат и может ли метод сообщать о прогрессе хода операции.

    Интерфейс (в Windows.Foundation) Вариант использования
    IAsyncAction Используется для асинхронного метода, который не возвращает результатов (никакие аргументы не отправляются обработчику завершения) и не сообщает о ходе выполнения операции.
    IAsyncActionWithProgress<TProgress> Используется для асинхронного метода, который не возвращает результатов, но сообщает о ходе выполнения операции, где <TProgress> это тип данных аргумента, который передается в обработчик прогресса операции.
    IAsyncOperation<TResult> Используется для асинхронного метода, который возвращает результаты типа <TResult>, но не сообщает о ходе выполнения операции.
    IAsyncOperationWithProgress<TResult, TProgress=""> Используется для асинхронного метода, который возвращает результат типа <TResult> и сообщает о ходе выолнения операции с помощью аргумента типа <TProgress> обработчику прогресса.

    Выбрав тип асинхронного метода, который мы создаем, теперь мы можем исполнять код метода в другом потоке. Здесь можно использовать потоки напрямую, использовать пул потоков, предоставляемый ), однако, имеются и более высокоуровневые конструкции и в C#/VB и в C++, которые значительно упрощают работу.

    Асинхронные методы в C# и Visual Basic

    В C# и Visual Basic имеется класс ) для данной цели. Объект Task, создается с помощью одного из статических методов Task.Run. Мы передаем этому механизму анонимную функцию (она называется делегатом в .NET, определяется с помощью лямбда-выражения =>), которая содержит код для выполнения. Для того, чтобы потом конвертировать объект Task в подходящий асинхронный интерфейс WinRT, мы вызываем методы расширения задачи AsAsyncAction or AsAsyncOperation. Вот как это выглядит:

    public IAsyncOperation<string>
      SomeMethodAsync(int id)
      {
      var task = Task.Run<string>
        ( () => //	() => в C# это то же, что function () в JS
        {
        return "Here is a string.";
        });
        return task.AsAsyncOperation();
        }

    Внутри кода задачи производятся любые асинхронные операции (для чего мы используем ключевое слово C# await, как описано в вышеупомянутых сообщениях из блога), делегат должен быть маркирован с помощью async:

    public IAsyncOperation<string>
      SomeMethodAsync(int id)
      {
      var task = Task.Run<string>
        (async () =>
        {
        var idString = await GetMyStringAsync(id); //await делает асинхронный вызов выглядящим как синхронный
        return idString;
        });
        return task.AsAsyncOperation();
        }

    Обратите внимание на то, что ) и один из его методов Run, который подходит под нужное асинхронное поведение. Вызов Task.AsAsyncOperation в конце не нужен здесь, так как AsyncInfo.Run уже предоставляет соответствующий интерфейс:

    public IAsyncOperation<string>
      SomeMethodAsync(int id)
      {
      return AsyncInfo.Run<string>
        (async (token) =>
        {
        var idString = await GetMyStringAsync(id);
        token.ThrowIfCancellationRequested();
        return idString;
        });
        }

    В этом коде ). Для поддержки возможности отмены оерации, вы должны периодически вызывать метод маркера отмены ThrowIfCancellationRequested. Это позволяет распознать ситуацию, когда механизм, вызывавший асинхронный метод, отменил его (например, вызовом метода promise-объекта cancel). Так как отмена выполнения операции обычно инициируется пользователем, нет необходимости вызывать ThrowIfCancellationRequested его слишком часто. Если вызывать его каждые 50 миллисекунд или около того, приложение будет отлично реагировать на действия пользователя.

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

    Обратите внимание на то, что WinRT-методы могут принимать маркеры отмены из-за перегрузки AsTask. Вместо этого:

      await SomeWinRTMethodAsync();

    Вы можете использовать это:

     await SomeWinRTMethodAsync().AsTask(token);

    Во всяком случае, учитывая эти примеры, вот асинхронная версия CountFromZero, которая не поддерживает отмену:

     public static IAsyncOperation<double>
    CountFromZeroAsync(double max, double increment)
    {
    var task = Task.Run<double>
      (() =>
      {
      double sum = 0;
      for (double x = 0; x < max; x += increment)
    {
    sum += x;
    }
    return sum;
    }); 
    
    return task.AsAsyncOperation();
    }

    Интерфейс IAsyncOperation, возвращаемый этим методом, как и все асинхронные интерфейсты в Windows.Foundation, проецируется в JavaScript как promise-объект, таким образом, мы можем использовать обычный код для вызова методов и получения результатов (asyncVars это объект для хранения переменных):

    asyncVars.startCS = new Date();	
    var promiseCS = PixelCruncherCS.Tests.countFromZeroAsync(max, increment);
    promiseCS.done(function (sum) {	
    asyncVars.timeCS = new Date() - asyncVars.startCS;	
    asyncVars.sumCS = sum;	
    });

    Используя подобный код в упражнении "Image Manipulation" вы можете начать асинхронную операцию счета (использовав кнопку Counting Perf (Async)) и немедленно после этого открыть изображение и выполнить конверсию в оттенки серого в то же самое время.

    Асинхронные методы в C++

    Для реализации асинхронных методов в C++, нам нужно прийти к тому же конечному результату, что и в C# - к методу, который возвращает один из интерфейсов IAsync* и исполняет собственный внутренний код в другом потоке.

    Первая часть задачи весьма очевидна. Нам лишь нужно объявить метод с использованием типов C++ (здесь это показано в C++ коде; объявление класса в Grayscale.h выглядит точно так же):

    using namespace Windows::Foundation;
            IAsyncOperation<double>
              ^ Tests::CountFromZeroAsync(double max, double increment)

    Аналог класса ), его можно найти в том, что называется Среда выполнения с параллелизмом для C++ (Parallel Patterns Library for C++, PPL) или Concurrency Runtime, ее пространство имен – concurrency (http://msdn.microsoft.com/library/windows/apps/dd492819.aspx) (используйте #include <ppltasks.h> и using namespace concurrency; вы можете так поступить в коде C++). Функция, которая создает задачу, называется create_async. Все, что нам нужно – это обернуть наш код в такую функцию, как показано ниже:

    IAsyncOperation<double>
      ^ Tests::CountFromZeroAsync(double max, double increment)
      {
      return create_async([max, increment]()
      {
      double sum = 0;
      for (double x = 0; x < max; x += increment)
    {
    sum += x;
    }
    return sum;
    });
    }

    Как и в C#, здесь есть дополнительные структуры, предназначенные для организации вложенных асинхронных операций, поддерживающие отмену операции и сообщения о ходе выполнения операции. Подробности вы можете найти в материале документации "Асинхронное программирование на языке C++" (http://msdn.microsoft.com/library/windows/apps/hh780559.aspx) и "Параллелизм задач" (http://msdn.microsoft.com/library/windows/apps/dd492427.aspx).

    Врезка: Объединение отложенных результатов

    Есть одна особенность упражнения "Image Manipulation", которая использует преимущества того, что управление всеми асинхронными операциями реализуется с помощью promise-объектов. В приложении мы отображает горизонтальный индикатор выполнения перед началом всех асинхронных операций по кнопке Counting Perf (Async):

    function testPerfAsync() {
    showProgress("progressAsync", true);
    //...
    }

    Мы хотим, чтобы этот элемент управления оставался видимым во время выполнения любых асинхронных операций. Это легко можно сделать с помощью WinJS.Promise.join. Что здесь интересно отметить, этот то, что мы уже можем вызывать методы then или done у этих отдельных promise-объектов, что просто означает, что ы соединили различные обработчики для этих отдельных операций. Обработчики, которые мы передаем join, лишь привязаны к исполнению всех запрошенных отложенных операций:

    promiseJS.done(function (sum) {
    // Вывод данных для рабочего процесса JS
    }
    
    promiseCS.done(function (sum) {
    // Вывод данных для компонента C#
    })
    
    promiseCPP.done(function (sum) {
    // Вывод данных для компонента C++
    });
    
    WinJS.Promise.join([promiseJS, promiseCS, promiseCPP]).done(function () {
    //	Скрыть индикатор выполнения когда все операции будут выполнены
    showProgress("progressAsync", false);
    });

    В этом коде мы можем видеть, насколько все упрощается благодаря заключению механизма сообщений рабочего процесса внутрь promise-объекта. Не сделав этго, мы нуждались бы в управлении одним флагом для указания на то, получены ли отложенные результаты (устанавливая его в true внутри join), и другим флагом для того, чтобы показать, что получен результат от рабочего процесса (устанавливая его в true внутри обработчика сообщений рабочего процесса). Внутри join нам понадобилось бы проверять, завершена ли операция рабочего процесса, прежде чем скрыть индикатор выполнения. Обработчик сообщения рабочего процесса дела бы то же самое, проверяя, завершены ли операции в join. Подобными взаимоотношениями можно управлять в маленьких масштабах, но с множеством параллельных асинхронных операций все станет слишком запутанным – и именно в этом причина создания promise-объекта в первом случае!

    Массивы, векторы и другие альтернативы

    Теперь, когда мы рассмотрели базовую структуру асинхронных методов в WinRT-компонентах, посмотрим, как мы можем создать асинхронный вариант синхронного метода Convert, который мы реализовали ранее. Для целей этого упражнения мы будем придерживаться C#-компонента.

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

    Слудующее, что мы можем попробовать, это превратить асинхронное действие в операцию, которая производит результат. Мы можем решить выбрать возвращаемый тип IAsyncOperation<Byte[]> (или эквивалент, возвращающий еще и данные о прогрессе операции), где метод создаст и заполнит массив для возврата. Проблема здесь, тем не менее, в том, что приложение , получившее массив, не знает, как получить к нему доступ. Очевидно, некоторая память была выделена для массива, но выделение произошло внутри компонента, а не внутри JavaScript, и в данной ситуации нет четких правил того, как следует поступать. Так как это верный путь для утечек памяти, возврат подобных массивов не поддерживается.

    Альтернатива для асинхронного метода заключается в возврате специального типа коллекции WinRT (для которого применены четкие правила освобождения памяти), такого, как IList<Byte> , который будет в JavaScript конвертирован в вектор, к которому, кроме того, можно получить доступ как к массиву. (Обратите внимание на то, что тип IList специфичен для .NET-языков; пошаговый пример по C++ показывает, как использовать вектора напрямую с помощью типа concurrent_vector.) Вот простой пример подобного метода:

     public static IAsyncOperation<IList<Byte>
       > CreateByteListAsync(int size)
       {
       var task = Task.Run<IList<Byte>
         >(() =>
         {
         Byte [] list = new Byte[size];
    
         for (int i = 0; i < size; i++)
    {
    list[i] = (Byte)(i % 256);
    }
    return list.ToList();
    });
    return task.AsAsyncOperation();
    }

    Применение этого подхода к процедуре для преобразования в оттенки серого, мы получаем ConvertPixelArrayAsync (смотрите PixelCruncherCS > ConvertGrayscale.cs), где DoGrayscale это основной код процедуры, разбитый на различные функции, третий параметр – это периодически вызываемая функция обратного вызова, которую мы можем использовать для обработки отмены:

     
    public IAsyncOperation<IList<Byte>
           > ConvertPixelArrayAsync([ReadOnlyArray()] Byte[] imageDataIn)
           {
           //Используем AsyncInfo для создания IAsyncOperation которая поддерживает отмену
           return AsyncInfo.Run<IList<Byte>
    >((token) => Task.Run<IList<Byte>
      >(() =>
      {
      Byte[] imageDataOut = new Byte[imageDataIn.Length]; DoGrayscale(imageDataIn, imageDataOut, () =>
      {
      token.ThrowIfCancellationRequested();
      });
      return imageDataOut.ToList();
      }, token));
      }

    Четвертый подход заключается в использовании шаблона, который применяется классом Windows.Graphics.Imaging.PixelDataProvider, который мы уже используем в упражнении "Image Manipulation". В функции setGrayscale (js/default.js), мы открываем файл, полученный от средства выбора файлов, и затем декодируем его с помощью BitmapDecoder.getPixelDataAsync. Результат этой операции – это PixelDataProvider, у которого есть метод detachPixelData который предоставляет нам пиксельный массив (некоторые участки кода опущены для краткости):

    function setGrayscale(componentType) {
    imageFile.openReadAsync().then(function (stream) {
    return Imaging.BitmapDecoder.createAsync(stream);
    }).then(function (decoderArg) {
    //Конфиругирование декодера ... [код опущен]
    return decoder.getPixelDataAsync();
    }).done(function (pixelProvider) {
    copyGrayscaleToCanvas(pixelProvider.detachPixelData(),
    decoder.pixelWidth, decoder.pixelHeight, componentType);
    });
          

    Похожая реализация нашей процедуры конвертации в оттенки серого, находится в PixelCruncherCS > ConvertGrayscale.cs, в функции ConvertArraysAsync. Она имеет тип IAsyncAction , так как она работает с массивом Grayscale.inputData (который следует сначала установить). Выходные данные доступны из Grayscale.detatchOutputData(). Вот, как выглядит код JavaScript:

    pc1.inputData = pixels;
    pc1.convertArraysAsync().done(function () {
    var data = pc1.detachOutputData()
    copyArrayToImgData(data, imgData);
    updateOutput(ctx, imgData, start);
    });

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

    В течение всего примера мы загружали данные изображения из файла, используя BitmapDecoder, и затем конвертируем полученные пиксели в оттенки серого в массиве, полученном с помощью метода createImageData элемента canvas. Как только данные окажутся внутри объекта данных изображения, мы можем вызвать метод putImageData элемента canvas для их вывода на экран. Все это было изначально реализовано для показа взаимодействия с этим элементом, включая сохранение его содержимого в файл. Это было нормально для Главы 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", где нашей основной темой была работа с графикой. Однако, если нам нужна лишь конверсия файла изображения в оттенки серого, использование элемента canvas – это не лучший подход.

    Ключевая проблема, с которой мы здесь столкнулись, заключается в том, что метод putImageData этого элемента принимает только объект ImageData, созданный методом createImageData все того же элемента canvas. Элемент не позволяет вам создать и вывести отдельный пиксельный массив, не позволяет и вставить произвольный массив в свойство ImageData.data. Единственный способ, которым можно воспользоваться, это прямая запись данных в массив ImageData.data.

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

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

    Возвращаясь назад, можно вспомнить, что общая цель демонстрации была в конвертации файла изображения в оттенки серого и показе результатов на экране. Использование элемента canvas – это лишь деталь реализации – мы можем достигнуть тех же результатов другим путем. Например, вместо конверсии пикселей в массиве в памяти, мы можем создать временный файл, используя вместо этого класс Windows.Graphics.Image.BitmapEncoder, так же, как мы использовали его в функции SaveGrayscale, которая уже есть в приложении. Мы просто передали ей массив сконвертированных пикселей, вместо того, чтобы брать эти пиксели снова из элемента canvas. Тогда мы можем использовать URL.createObjectURL или URI ms-appdata:/// для отображения его в элементе img. Подобное будет производиться гораздо быстрее, так как метод putImageData элемента canvas требует много времени для выполнения, гораздо больше, чем занимают процедуры конверсии в наших компонентах.

    В том же духе, нет особой причины, по которой мы не могли бы разместить весь этот процесс внутри компонента. Только та часть, которая работает с пользовательским интерфейсом, должна быть в JavaScript, но все остальное может быть написано на другом языке. Например, зачем беспокоить себя передачей пиксельного массива между JavaScript и WinRT-компонентом? Как только мы получили исходный StorageFile из средства выбора файлов, мы можем передать его напрямую в компонент. Затем он может использовать BitmapDecoder для получения пиксельноого потока, конвертировать его, затем создать временный файл и записать конвертированные пиксели обратно, используя BitmapEncoder, передать обратно объект StorageFile для временного файла, на основе которого мы можем установить img src. Таким образом, пиксельные данные никаогда не покидают компонент и никогда не копируются между буферами в памяти. Это должно привести и к более высокой производительности и к меньшему потреблению памяти.

    Для этого в проекте PixelCruncherCS в упражнении "Image Manipulation" есть еще один асинхронный метод, который называется ConvertGrayscalFileAsync и выполняет в точности то, о чем я сказал выше:

    public static IAsyncOperation<StorageFile>
       ConvertGrayscaleFileAsync(StorageFile file)
          {
      return AsyncInfo.Run<StorageFile>
      ((token) => Task.Run<StorageFile>(async () =>	
    {	
    StorageFile fileOut = null;	
    try
    {
    //Открыть файл и прочесть пиксельные данные
    using (IRandomAccessStream stream = await file.OpenReadAsync())
    {
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream); PixelDataProvider pp = await decoder.GetPixelDataAsync();
    Byte[] pixels = pp.DetachPixelData();
    
    //Мы знаем, что наш метод может произвести конверсию там же, где находятся данные
    //поэтому нам не нужно создавать копию данных
    DoGrayscale(pixels, pixels);
    
    //Сохранить временный файл.
    ApplicationData appdata = ApplicationData.Current;
    
    fileOut = await appdata.TemporaryFolder.CreateFileAsync
    ( "ImageManipulation_GrayscaleConversion.png", CreationCollisionOption.ReplaceExisting);
    
    using (IRandomAccessStream streamOut =	
    await fileOut.OpenAsync(FileAccessMode.ReadWrite))	
    {	
    BitmapEncoder encoder = await BitmapEncoder.CreateAsync(
    BitmapEncoder.PngEncoderId, streamOut);	
    
    encoder.SetPixelData(decoder.BitmapPixelFormat, decoder.BitmapAlphaMode,
     decoder.PixelWidth, decoder.PixelHeight,
    decoder.DpiX, decoder.DpiY, pixels);
    
    await encoder.FlushAsync();
    }
    }
    }
    catch
    {
    //Произошла ошибка; очистим fileOut 
    fileOut = null;
    }
    
    //Наконец, возвращаем созданный StorageFile, что делает удобным для вызывающей процедуры	
    //скопировать его куда угодно, использовать в вызове наподобие URL.createObjectURL, или сослаться
    //на него с помощью конструкции "ms-appdata:///temp" + fileOut.Name	
    
    
    return fileOut;	
    }));	
    }	

    В этом коде, в сравнении с эквивалентным кодом на JavaScript мы можем видеть, что ключевое слово await в C# очень упрощает работу с асинхронными методами – они выглядят так, как будто являются синхронными. Это одно из потенциальных преимуществ написания кода в компоненте! Другая важная деталь, на которую нужно обратить внимание, это использование операций с потоками. Потоки, в сравнении с другими типами, являются высвобождаемыми (disposable) (у них есть интерфейс IDisposable) и должны быть очищены после использования, иначе файлы остаются открытыми и вы можете столкнуться с исключением отказа в доступе или с другим странным поведением. Выражение using самостоятельно реализует подобную логику очистки.

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

    PixelCruncherCS.Grayscale.convertGrayscaleFileAsync(imageFile).done(function (tempFile) {
    if (tempFile != null) {	
    document.getElementById("image2").src = "ms-appdata:///temp/" + tempFile.name;	
    }	
    });	

    Строки с URI можно так же заменить в этом случае:

    var uri = URL.createObjectURL(tempFile, { oneTimeOnly: true });
    document.getElementById("image2").src = uri;

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

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

    Проекции в JavaScript

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

    Начнем с именования. Мы уже видели, что в проекты приложений на JavaScript компоненты добавляются в виде ссылок, после чего пространство имен компонента становится доступным в JavaScript, других объявлений не требуется. Пространство имен и классы в компоненте напрямую доступны в JavaScript. Однако, меняются имена методов, свойств и событий. Хотя пространство имен и имена классов проецируются в том виде, в котором они существуют в компоненте, имена методов и свойств (в том числе – члены структур (struct) и перечислений (enum)) конвертируются с использованием "верблюжьего" стиля: имена TestMethod и TestProperty в компоненте принимают вид testMethod и testProperty в JavaScript. Подобное изменение регистра символов может привести к необычному эффекту, как когда имя компонента начинается с двух заглавных букв, как, например, когда UIProperty, видно в JavaScript как uIProperty.

    С другой стороны, имена событий приводятся к использованию только прописных букв, что соответствует соглашению об именовании, принятому в JavaScript. Событие, имеющее имя SignificantValueChanged в компоненте превращается в significantvaluechanged в JavaScript. Это имя, в нижнем регистре, используется с addEventListener, и классу, которые его предоставляет, так же будет задано свойство, имя которого состоит из данного имени с префиксом on, как, например, onsignificantvaluechanged. Важная особенность в работе с событиями заключается в том, что иногда нужно явным образом вызывать removeEventListener для предотвращения утечек памяти. Подробнее об этом – в Главе 3 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript". В контексте данной лекции, подобное применимо и к вашим собственным WinRT-компонентам.

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

    Вот два ограничения, которые мы упоминали ранее, и которые полезно будет повторить сейчас. Первое – это то, что WinRT-компоненты не могут работать с пользовательским интерфейсом приложения, написанного на JavaScript. Это так, потому что приложение не может получить поверхность для вывода любого рода, которую может использовать компонент. Второе – это то, что JavaScript может различать перегруженные методы лишь по их арности (количеству параметров), а не по типу. Если компонент предлагает перегрузки, отличающиеся лишь по типу, JavaScript может получить доступ только к тем из них, которые помечены как варианты по умолчанию.

    Далее, мы приходим к вопросу о типах данных, что всегда интересно, когда речь идет о взаимодействии между разными языками. Вообще говоря, то, что вы видите в JavaScript, обычно соответствует тому, что есть в компоненте. Тип WinRT DateTime становится типом JavaScript Date, числовые значения становятся типом Number, bool превращается в Boolean, строки остаются строками и так далее. Некоторые типы WinRT, наподобие IMapView и IPropertySet, просто переходят в JavaScript напрямую как объектные типы, так как у них нет внутренних эквивалентов в нем. Вот еще некоторые, более интересные, преобразования:

  • Асинхронные операции в компоненте, которые возвращают интерфейс наподобие IAsyncOperation, проецируются в JavaScript как promise-объекты.
  • Так как JavaScript не имеет концепции struct, которая есть в C#, VB, и C++, структуры из WinRT-компонентов появляются в JavaScript как объекты с полями структуры в качестве членов. Похожим образом, для вызова WinRT-компонента, который принимает аргумент struct, JavaScript-приложение создает объект с полями, представленными членами и передает этот объект вместо структуры. Обратите внимание, что имена членов struct в JavaScript конвертируются с использованием "верблюжьего" стиля.
  • Некоторые типы коллекций, таких, как IVector, появляеются в JavaScript как массивы, но с различными методами. Таким образом, доступ к коллекции можно получить с помощью оператора массива [ ], но у такого массива будут другие методы. Будьте осторожны, таким образом, передавая эти массивы функциям манипуляции JavaScript, которые подразумевают наличие у массивов стандартных методов.
  • Перечисления преобразовываются в объекты с использованием свойств, соответствующих значениям перечисления, имена которых преобразуются к "верблюжьему" стилю, значения приводятся к JavaScript-типу Number.
  • API WinRT иногда возвращают типы Int64 (в виде отдельных значений или в structs), для которых нет эквивалентов в JavaScript. Однако, 64-битные типы в JavaScript сохраняются, поэтому вы можете передать их в WinRT при другом вызове. Однако, если вы модифицируете переменную, хранящую подобное значение, даже с использованием чего-то простого, вроде оператора ++, она будет конвертирована в тип JavaScript Number. Подобое значение не будет принтято методами, ожидающими Int64.
  • Если метод компонента предоставляет несколько выходных параметров, они видны в JavaScript как объект с этими различными значениями. Четкого стандартна для подобного преобразования в JavaScript нет, поэтому лучше стараться не создавать компоненты, имеющие подобную структуру.
  • Суть заключается в том, что уровень проекции пытается сделать WinRT-компоненты, написанные на любом другом языке выглядящими и работающими так, как будто они принадлежат JavaScript, и использование которых не будет вызывать сложностей..

    Сценарии для WinRT-компонентов

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

    Повышение производительности

    Увеличение производительности приложений для Магазина Windows, написанных на HTML, CSS и JavaScript, это один из основных сценариев передачи некоторой части работы на WinRT-компонентам.

    Оценивая производительность приложения, обратите особое внимание на те области, где задействованы интенсивные вычисления или передача больших объемов данных. Например, если вы решаете реализовать расширенный экран-заставку, возможно, вы сможете уменьшить время, которое должен ждать пользователь (особенно – при первом запуске) до тех пор, пока приложение не активируется. Любая другая ситуация, где пользователю может понадобиться ждать – там, где у него есть возможность делать что-то более полезное, чем наблюдение за индикатором прогресса – это хорошее место для использования, если это возможно, высокопроизводительных компонентов. Очевидно, есть сценарии, где сама по себе производительность приложения не так влияет на скорость работы с ним, как сетевые задержки, но как только вы получили данные от некоего сервиса, вы быстрее можете произвести их предварительную обработку в компоненте, а не в JavaScript.

    Другое хорошее место для использования высокопроизводительных компонентов – это действия, выполняемые при запуске приложения, особенно если при первом запуске требуется много работы. Например, пакет приложения может включать в себя большой объем сжатых данных для уменьшения размера данных, загружаемых из Магазина Windows, но эти данные нужно распаковать при первом запуске. WinRT-компонент может значительно сократить время инициализации. Если компонент использует API WinRT для записи данных в папки приложения, все эти данные так же доступны и из JavaScript посредством тех же самых API.

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

    Одно из мест, где очень важна производительность, это фоновые задачи. Как разьяснено в Главе 2, фоновые задачи ограничены несколькими секундами процессорного времени в каждые 15 минут. Поэтому вы сможете сделать гораздо больше в фоновой задаче, написанной на высокопроизводительном языке, нежели в задаче, написанной на JavaScript.

    Структура компонента с подобной задачей не отличается от любого другого компонента, как показано в компоненте " C# Tasks" в примере "Фоновые задачи" (http://code.msdn.microsoft.com/windowsapps/Background-Task-Sample-9209ade9). Каждый из классов в пространстве имен Tasks маркирован как public и sealed, и так как компонент виден в JavaScript-проекте посредством ссылки, имена классов (и их открытые методы и свойствва), так же находятся в пространстве имен JavaScript. В результате их имена без проблем могут быть заданы свойству BackgroundTaskBuild.taskEntryPoint.

    Другой пример использования той же самой техники доступен в примере "Фоновое определение состояния сети" (http://code.msdn.microsoft.com/windowsapps/Network-status-background-957eb3eb).

    Кое что, о чем мы не говорили в Главе 2, но о чем самое время поговорить теперь, это то, что при создании WinRT-компонента для данной задачи, класс, который реализует фоновую задачу, должен быть унаследован от ) и реализовывать его метод Run. Этот метод вызывается при активации фоновой задачи. Это можно увидеть в примере "Фоновое определение состояния сети", где вся C#-реализация компонента заключена в нескольких десятках строк кода (проект NetworkStatusTask > BackgroundTask.cs; некоторые комментарии и команды тестового вывода опущены):

    namespace NetworkStatusTask
    {
    public sealed class NetworkStatusBackgroundTask : IBackgroundTask
    {
    ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
    
    // Метод Run – это точка входа для фоновой задачи.
    public void Run(IBackgroundTaskInstance taskInstance)
    {
    // Связывание обработчика отмены операции с фоновой задачей.
    taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
    try
    {
    ConnectionProfile profile =
    NetworkInformation.GetInternetConnectionProfile();
    if (profile == null)
    {
    localSettings.Values["InternetProfile"] = "Not connected to Internet";
    localSettings.Values["NetworkAdapterId"] = "Not connected to Internet";
    }
    else
    {
    localSettings.Values["InternetProfile"] = profile.ProfileName;
    
    var networkAdapterInfo = profile.NetworkAdapter;
    if (networkAdapterInfo == null)
    {
    localSettings.Values["NetworkAdapterId"] = "Not connected to Internet";
    }
    else
    {
    localSettings.Values["NetworkAdapterId"] =
    networkAdapterInfo.NetworkAdapterId.ToString();
    }
    }
    }
    catch (Exception e)
    {
    // Тестовый вывод опущен
    }
    }
    
    // Обработка отмены фоновой задачи.
    private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
    // Тестовый вывод опущен
    }
    }
    }

    Вы можете видеть, что метод Run принимает аргумент, с помощью которого он может зарегистрировать обработчик для отмены задачи. Вышеприведенный код ничего значимого не делает, так как задача исполняет лишь небольшое количество кода. Задачи C# в примере "Фоновые задачи", с другой стороны, имитируют длительные операции, в подобном случае используется обработчик для установки флага, который может остановить эти операции.

    Доступ к дополнительным API

    Среди API DOM, WinJS, библиотек сторонних разработчиков и внутренних возможностей JavaScript, у JavaScript-разработчиков нет недостатка в API для использования в своих приложениях. В то же время, есть огромное количество API .NET (http://msdn.microsoft.com/library/windows/apps/br230232.aspx) и Win32/COM (http://msdn.microsoft.com/library/windows/apps/br205757.aspx), которые доступны в приложениях на C#, VB, и C++ и не доступны напрямую в JavaScript, в том числе API DirectX b Media Foundation.

    За исключением API, которые воздействуют на пользовательский интерфейс или поверхности рисования (а именно, Direct2D и Direct3D), WinRT-компоненты могут делать подобные функции, или, скорее, высокоуровневые операции, построенные на их основе, доступными для приложений, написанных на JavaScript.

    В материале "Создание собственных компонентов среды выполнения Windows для разработки эффективных приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/08/13/windows-metro.aspx) блога для разрабочтиков приложений для Windows 8, даны некоторые советы по этому поводу. Здесь показано, как использовать API System.IO.Compression в .NET для работы с ZIP-файлами, и API XAudio (часть DirectX) для обхода элемента HTML audio и низкоуровневого проигрывания аудиофайлов. В Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" мы касались этого вопроса, когда, как бы мы ни старались, между воспроизведением записей в элементе audio, всегда между ними была небольшая пауза. Это происходит потому, что некоторое время занимает перевод всех операций элемента в вызовы системного API XAudio. Работая напрямую с тем же самым API, вы можете полностью решить эту проблему (Тем не менее, Microsoft знает о подобном поведении элемента audio и, весьма вероятно, улучшит его производительность в будущем.)

    Подобный подход так же можно использовать для взаимодйствия с внешними устройствами, которые не представлены в API WinRT, но представлены в Win32/COM. Мы видели подобное с применением ), в Главе 4 (речь об этом идет и в вышеупомянутом материале блога).

    Другой очень простой пример – это создание компонента для ответа на частый вопрос: "Как создать GUID в JavaScript?". Хотя вы можете реализовать процедуру для создания GUID-строки из случайных чисел, это неподходящий GUID, так как нет гарантии его уникальности (в конце концов, GUID – это Глобальный уникальный идентификатор (Globally Unique Identifier). Для того, чтобы сделать все как нужно, используйте API Win32 CoGreatGuid (http://msdn.microsoft.com/library/windows/apps/ms688568.aspx), для чего вы можете создать очень простую оболочку на C++.

    "Из пушки по воробьям?" Некоторые разработчики говорят, что разбираться со всеми тонкостями создания WinRT-компонентов только для того, чтобы вызывать один метод наподобие CoCreateGuid – это слишком тяжеловесное решение. Однако, учитывая простоту реализации базового WinRT-компонента, которую мы видели в данной лекции, все, что вы действительно делаете с компонентом, это создаете многоязычную структуру, посредством которой вы можете использовать все возможности каждого из языков. Накладные расходы, на самом деле, невелики. Например, при Release-построении C++-компонента из раздела "Быстрый старт №2" получилась DLL объемом 39 Кб и 3-килобайтный .winmd-файл.

    Использование WinRT-компонентов, таким образом, применимо и к использованию COM DLL, которые содержат API сторонних разработчиков, наподобие библиотек кода. Вы можете использовать их в приложениях для Магазина Windows при условии, что они отвечают трем требованиям:

  • DLL находится в пакете приложения.
  • DLL использует только те API Win32/COM (http://msdn.microsoft.com/library/windows/apps/br205757.aspx), которые разрешено использовать с приложениями для Магазина Windows. В противном случае приложение не пройдет сертификацию в Магазине Windows.
  • DLL должна реализовывать то, что называется Regfree COM, то есть, для ее работы не должны требоваться какие-либо записи в системном реестре . (Приложения для Магазина Windows не имеют доступа к реестру, и, таким образом, не могут регистрировать там COM-библиотеки). Лучший материал на эту тему, который я нашел, это "Упрощение развертывания приложения с ClickOnce и Registration-Free COM" (http://msdn.microsoft.com/en-us/magazine/cc188708.aspx) в MSDN Magazine.
  • Если все эти требования соблюдены, приложение может использовать функцию ) из компонента для создания экземпляров объектов из нужной ему DLL.

    Скрытие кода и защита интеллектуальной собственности.

    В Главе 1 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript" мы видели, как загружаются и исполняются приложения. Сейчас, возможно, вы уже понимаете, что как старательно Windows ни пытается закрыть пакеты приложений от случайного доступа, весь код приложений находится на устройстве и пользователь может получить к нему доступ. Другими словами, знайте, что ваш код так же видим в пакете приложения, как если бы это был код страницы в веб-браузере, когда в нем выбирают команду Показать код

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

    Как только разработчики начали экспериментировать с приложениями для Магазина Windows , написанными на HTML, CSS и JavaScript, они задавали вопросы о том, как им защитить свой код. На самом деле, разработчики, использующие C# и Visual Basic задают похожие вопросы, хотя эти языки компилируются в IL (промежуточный язык, intermediate language), существует множество декомпиляторов, которые производят исходный код из IL, есть и программы, которые позволяют раскрывать минимизированный JavaScript-код. Ни JavaScript, ни .NET-языки не умеют достаточно хорошо скрывать подробности реализации приложений.

    Код, написанный на C++ и скомпилированный в машинный код, гораздо хуже поддается методам обратной инженерии, хотя и это задача вполне выполнимая для некоторых (в таком случае, вы можете спросить у них, почему бы им не писать собственный код!). Тем не менее, это лучшая защита для кода, который существует на клиентском компьютере. А единственный реальный способ защитить алгоритмы – это хранить их на удаленном сервере.

    Если остальное приложение написано на языке, отличном от C++, в особенности – на JavaScript, знайте, что довольно легко выяснить и особенности реализации интерфейса компонента. Вопрос здесь в том, может ли злоумышленник может использовать знание интерфейса компонента для использования этого компонента в его собственных приложениях. Короткий ответ: "Да, может", - так как ваш код покажет ему, как это сделать. В подобных случаях более гибкий и почти непробиваемый подход заключается в том, чтобы поставщик компонента в индивидуальном порядке управлял лицензиями разработчиков. У компонента может быть предусмотрена процедура инициализации, необходимая для включения остальной его функциональности. В этом вызове он может сравнить сведения о пакете приложения, полученные посредством класса Windows.ApplicationModel.PackageId с теми, которые известны ему, с учетом того, что уникальность параметров, идентифицирующих приложение, поддерживается Магазином Windows. Вот нескольуко вариантов проверки:

  • Провести проверку с помощью сетевого сервиса. Это может потребовать наличия сетевого соединения, что, в большинстве сценариев, не проблема. Помните лишь о шифровании данных, которые вы отправляете по сети для того, чтобы избежать их перехвата с помощью инструментов наподобие Fiddler!
  • Сверить данные с зашифрованной информацией, которая находится в самом компоненте. Таким образом, компонент компилируется отдельно для каждой лицензии. Подобный подход сложнее всего для взлома.
  • Произвести сверку с зашифрованным файлом лицензии, распространяющимся вместе с компонентом и уникальным для лицензиата (содержащим, например, имя приложения и издателя). Возможно, это самое простое решение, так как даже если файл лицензии скопирован из пакета лицензированного приложения, содержащаяся в нем информация не будет совпадать с информацией о пакете другого приложения во время его выполнения. Алгоритм шифрования может содержаться внутри скомпилированного компонента, поэтому их будет сложно извлечь для того, чтобы взломать файл с лицензией – не невозможно, но очень сложно. Другое приложение может использовать такой компонент, только если оно использует ту же самую информацию о пакете приложения, что невозможно для приложений, загруженных из Магазина Windows, но возможно для разработчиков, при самостоятельной работе с приложениями, или для недобросовестных организаций.
  • В итоге, таким образом, учитывайте, что Windows сама по себе не может гарантировать безопасность кода приложений на клиентском устройстве. Дальнейшая защита должна быть реализована приложением самостоятельно.

    Библиотеки компонентов

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

    Хороший пример библиотеки – это Notifications Extensions Library, которую мы видели в нескольких примерах, в Главе 2: "Плитки и индикаторы событий приложения" (http://code.msdn.microsoft.com/windowsapps/App-tiles-and-badges-sample-5fc49148), "Приложения экрана блокировки" (http://code.msdn.microsoft.com/windowsapps/Lock-screen-apps-sample-9843dc3a), "Запланированные обновления" (http://code.msdn.microsoft.com/windowsapps/Scheduled-notifications-da477093), "Дополнительные плитки" (http://code.msdn.microsoft.com/windowsapps/Secondary-Tiles-Sample-edf2a178) и "Всплывающие уведомления" (http://code.msdn.microsoft.com/windowsapps/toast-notifications-sample-52eeba29). Эта библиотека содержит множество классов с соответствующими методами и свойствами, и так как все эти методы маленькие и быстрые, они реализованы в синхронном виде.

    В случае с примерами, библиотека Notifications Extensions Library включена в каждый проект, который ее использует, в виде исходного кода. Более вероятно, однако, в особенности если вы создаете коммерческий проект и следуете руководству "Практическое руководство. Создание пакета средств разработки программного обеспечения" (http://msdn.microsoft.com/library/windows/apps/hh768146.aspx), вы будете передавать лишь скомпилированные DLL, и/или WinMD-файлы вашим клиентам. Клиенты будут добавлять эти библиотеки в свои проекты, в итоге, они будут упакованы вместе с приложениями.

    В подобном случае не забудьте предоставить отдельные компоненты, скомпилированные для целевых платформ x86, x64, и ARM для компонентов, написанных на C++.

    Параллелизм

    Мы уже видели, что рабочие веб-процессы и WinRT-компоненты с асинхронными методами могут работать бок о бок для передачи задач в различные потоки. Если вам действительно нужно подобное, вы можете спроектировать все приложение на основе параллельного выполнения, используя эти механизмы, запуская одновременно несколько асинхронных операций, возможно, с помощью нескольких рабочих процессов. Компоненты WinRT так же могут использовать множественные вызовы Task.Run, а не только отдельные, как мы видели.

    Если посмотреть глубже, то WinRT-компоненты могут использовать API в ) для получения доступа к пулу потоков, а так же те API в , которые работают с семафорами и с другими событиями, связанными с многопоточностью. Подробности подобной работы лежат за пределами данного курса, но я хочу упомянуть их здесь, так как многие встроенные API WinRT их использует, и ваши компоненты могут делать то же самое. И, хотя здесь не идет речь о компонентах, пример "Пул потоков" (http://code.msdn.microsoft.com/windowsapps/Pool-Sample-5aa60454) предоставляет ценную информацию для того, чтобы начать разбираться с этим вопросом.

    Что мы только что изучили

  • Приложения для Магазина Windows не обязательно писать на каком-то одном языке. С помощью WinRT-компонентов приложения могут эффективно использовать язык, который наилучшим образом подходит для решения конкретной задачи. Компоненты, однако, не могут работать с пользовательским интерфейсом от имени приложения, написанного на HTML, CSS и JavaScript.
  • Причины для применения нескольких языков в приложении включают в себя улучшение производительности, получение доступа к дополнительным API (включая библиотеки сторонних разработчиков), которые при обычном режиме не доступны JavaScript, скрытие кода для защиты интеллектуальной собственности, создание модульных библиотек компонентов, которые могут использовать приложения, написанные на других языках и эффективное распараллеливание задач.
  • Для решения задач, требующих интенсивных вычислений, компоненты, написанные на C#/VB позволяют достичь повышения производительности примерно на 15% в сравнении с JavaScript-кодом, а компоненты, написанные на C++, примерно на 25%. При тестировании производительности не забудьте выполнить Release-построение приложения и запустить приложение вне отладчика, в противном случае вы увидите другие результаты для разных языков.
  • Приложения для Магазина Windows могут использовать рабочие веб-процессы для создания асинхронных процедур, которые выполняются отдельно от потока пользовательского интерфейса, вы можете заключать эти рабочие процессы в оболочку promise-объектов WinJS для того, чтобы работать с ними так же, как с асинхронными методами WinRT.
  • Асинхронные методы так же могут быть реализованы в WinRT-компонентах с использованием задач, параллелизма и API пула потоков. В сравнении с рабочими веб-процессами, подобные асинхронные методы отличаются более высокой скоростью отзыва, так как их внутреннее устройство соответствует устройству методов.
  • Вне зависимости от языка, на котором написан компонент, уровень проекции JavaScript переводит некоторые из его структур в вид, естественный для JavaScript, включая изменение регистра имен и конверсию типов данных.
  • В связи с сущностью обработчиков событий, которые пересекают границу между JavaScript и WinRT-компонентами, приложения должны предупреждать утечку памяти, вызывая removeEventListener для событий, исходящих из API WinRT или WinRT-компонентов, когда прослушивают их лишь временно.
  • Страницы:

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

    Структура компонента

  • Выходные файлы компонентов на C#/VB –.NET-сборка с метаданными Windows представлены в виде файла .winmd; у C++ компонентов это DLL-файл с кодом и .winmd-файл с метаданными.
  • Приложения, которые используют компоненты, должны включать файлы.winmd/DLL в состав проектов, на них должны быть добавлены ссылки. Включать в состав проектов исходный код компонентов необязательно.
  • Классы компонентов, которые могут быть использованы из других языков, называются активирумыми (activatable) классами. В C# они должны быть маркированы как public sealed, в Visual Basic как Public NotInheritable, и в C++ как public ref sealed. Компонент должен иметь хотя бы один активируемый класс для того, чтобы им можно было пользоваться из других языков.
  • Классы могут иметь статические (static) члены (свойства и методы), которые можно использовать без создания экземпляра объекта этого класса.
  • Компоненты могут содержать множество общедоступных активируемых классов, так же как и дополнительных, внутренних классов. Все общедоступные классы должны располагаться в одном и том же корневом пространстве имен, которое имеет то же имя, что и файл метаданных компонента.
  • По умолчанию, все общедоступные классы в компоненты видимы из других языков. Класс может быть скрыт от JavaScript путем применения атрибута ) (то есть, перед объявлением класса должно идти [Windows.Foundation.Metadata.WebHostHidden] в C# или [Windows::Foundation::Metadata::WebHostHidden] в C++. Это применимо к классам, которые работают с пользовательским интерфейсом (подобная функциональность не может быть использована совместно с JavaScript, как, например, все пространство имен Windows.Xaml в WinRT) или к классам, функциональность которых совпадает со встроенными возможностями JavaScript (такими, как Windows.Data.Json).
  • Для того, чтобы узнать некоторые дополнительные сведения о структуре компонентов, смотрите следующие примеры из Windows SDK (все они используют WRL, смотрите врезку "Библиотека шаблонов C++ среды выполнения Windows (WRL)"):
  • "Создание внутрипроцессного WinRT-компонента (C++/CX)" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-460a535f)
  • "Создание внутрипроцессного WinRT-компонента (C#)" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-367fada2)
  • "Создание EXE WinRT-компонента на C++" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-ed84af9d)
  • "Создание DLL WinRT-компонента на C++" (http://code.msdn.microsoft.com/windowsapps/Creating-a-Windows-Runtime-6c399797)
  • Типы

  • Внутри компонента вы можете использовать собственные типы данных языка (то есть, .NET-типы и типы C++). На уровне интерфейса компонента (Application Binary Interface, или ABI), вы должны использовать WinRT-типы или собственные типы языка, которые совместимы с WinRT-типами. В противном случае их значения не смогут быть спроецированы в другие языки. В C++, типы WinRT существуют в пространстве имен ), посмотрите так же материал "Система типов (C++/CX)" (http://msdn.microsoft.com/library/windows/apps/hh755822.aspx). В C#/VB, они существуют в пространстве имен System, посмотрите материал "Сопоставление типов .NET Framework с типами среды выполнения Windows" (http://msdn.microsoft.com/library/windows/apps/hh995050.aspx)
  • Компоненты могут использовать структуры, созданные с помощью типов WinRT, которые проецируются в JavaScript как объекты со свойствами, соответствующими членам объекта struct.
  • Коллекции должны использовать специальные типы WinRT, которые находятся в), такие, как IVector, IMap (и IMapView), и IPropertySet. Именно поэтому нам так часто встречаются векторы.
  • К массивам применимы специальные условия, так как их можно отправлять лишь в одном направлении, как мы видели в кратких руководствах. Таким, образом, каждый из них может быть маркирован либо как массив только для чтения, либо как массив только для записи. Смотрите материал "Передача массивов в компонент среды выполнения Windows" (http://msdn.microsoft.com/library/windows/apps/hh975353.aspx). У массивов так же есть ограничение на использование с асинхронными методами, так как выходной массив не возвращается вызывающему объекту при завершении асинхронной операции. Больше об этом мы поговорим в разделе "Реализация асинхронных методов" ниже.
  • Реализация компонентов

  • Создавая перегруженные методы, убедитесь, что арность (arity) (количество аргументов) у каждой из них различается, так как JavaScript не может определять перегруженные методы лишь по типу. Если вы создаете несколько перегрузок с тем же количеством аргументов, одна из них должна быть отмечена атрибутом ) для того, чтобы при использовании JavaScript-проекции было известно, какой из них нужно использовать.
  • Делегат (анонимная функция в определении JavaScript), это объект функции. Делегаты используются для событий, обратных вызовов и асинхронных методов. Объявление делегата ведет к определению сигнатуры функции.
  • Ключевым словом event отмечается общедоступный член специального типа делегата в виде событие. Делегаты события – сигнатура для обработчика – могут иметь тип (то есть, EventHandler<T>), что означает, что объект ).
  • Выдача исключения: используя ключевое слово throw в C#, VB, и C++ вы вызовете новый экземпляр объекта с типом исключения в пространстве имен System (http://msdn.microsoft.com/library/windows/apps/hh454070.aspx). В C++ используйте throw ref new с одним из типов исключений в пространстве имен Platform namespace, например, Platform::InvalidArgumentException (http://msdn.microsoft.com/library/windows/apps/hh755794.aspx). Это отобразится в JavaScript со сведениями о стеке, в поле сообщения исключения. Действительное сообщение от компонента появлится в диалоговом окне исключений Visual Studio
  • Реализация асинхронных методов

    Какими бы быстрыми не были процедуры, выполняемые на C# и C++, которые мы видели в разделе "Быстрый старт", факт заключается в том, что их исполнение может занимать более 50 мс при выполнении в потоке пользовательского интерфейса. Это – рекомендованный порог, достигнув которого, вы должны задуматься о том, чтобы сделать операцию асинхронной. Это означает исполнение кода в другом потоке, в итоге, пользовательский интерфейс не блокируется вовсе. Для того, чтобы показать основы, следующие разделы показывают, как реализовать асинхронную версию простой функции countFromZero , которую мы видели ранее, во врезке, где сравнивался управляемый и машинный код. Сначала мы реализуем это с помощью рабочего процесса, а потом – в C# и C++.

    Для C#/VB и C++ имеется подробная документация по созданию асинхронных методов. В базовом материале, который мы уже упоминали, "Создание компонентов среды выполнения Windows в C# и Visual Basic" (http://msdn.microsoft.com/library/windows/apps/br230301.aspx) есть подраздел "Асинхронные операции", посвященный этому. В материале "Пошаговое руководство. Создание основного компонента среды выполнения Windows в C++ и его вызов из кода JavaScript" (http://msdn.microsoft.com/library/windows/apps/hh755833.aspx) есть подраздел "Добавление в класс асинхронных открытых методов". Есть материал "Создание асинхронных операций в C++ для приложений для Магазина Windows" (http://msdn.microsoft.com/library/hh750082.aspx). Кроме того, имеется серия сообщений в Блоге для разработчиков приложений для Windows 8, которые посвящены как приложениям, так и компонентам. В частности, это следующие: "Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/03/28/windows.aspx), "Подробное рассмотрение WinRT и await" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/05/04/winrt-await.aspx), "Предоставление задач .NET в виде асинхронных операций WinRT" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/06/25/net-winrt.aspx). Попытка сравниться в глубине изложения с этими материалами была бы сопряжена с массой повторений, поэтому следующий раздел сосредоточен на создании асинхронной версии методов, работающих с пиксельными данными из кратких руководств и рассмотрению уроков, которые мы можем извлечь из опыта работы над ними.

    Конкретный сценарий, с которым мы работаем – проводим преобразование пиксельных данных в оттенки серого и отправляем результат в элемент canvas – позволит выявить ряд сложностей, с которыми полезно будет поработать, и которые не упомянуты напрямую в другой документации. Это включает в себя сложности с передачей массивов между приложениями и компонентами, что представляет интересный шаблон проектирования, которые используется некоторыми API WinRT. Тем не менее, решение приводит нас к чему-то вроде тупика, из-за ограничений элемента HTML canvas. Это заставляет нас задуматься об альтернативах, что является хорошим упражнением, так как вы, вероятно, столкнетесь с другими сложностями в вашей собственной работе над компонентами.

    Рабочие процессы JavaScript

    При работе исключительно с JavaScript, рабочие процессы – это тот способ, с помощью которого можно передать исполнение кода в другие потоки. Главное, что нужно здесь понять – это то, что взаимодействие между главным приложением (потоком пользовательского интерфейса) и рабочими процессами происходит посредством отдельных сообщений postMessage и с посощью связанного с ними события message. Таким образом, рабочие процессы не похожи на компоненты, в которых вы можете просто вызывать методы и получать результат их выполнения. Если вы хотите вызвать метод с конкретными аргументами внутри рабочего процесса, вы должны выполнять подобный вызов с помощью сообщения postMessage, которое содержит необходимые значения. С другой стороны, функция, которая вызвана внутри рабочего процесса, возвращает результат в главное приложение, так же вызывая postMessage.

    Например, в упражнении "Image Manipulation", я поместил функцию countFromZero в js/worker_count.js вместе с обработчиком сообщения, который служит как простой диспетчер метода:

    onmessage = function (e) {
    switch (e.data.method) {
    :
    case "countFromZero"
    countFromZero(e.data.max, e.data.increment);
    break;
    
    default:
    break;
    }
    };
    
    function countFromZero(max, increment) {
    var sum = 0;
    max = 10;
    
    for (var x = 0; x < max; x += increment) {
    sum += x;
    }
    
    postMessage({ method: "countFromZero", sum: sum });
    }

    Когда запускается этот рабочий процесс, единственный код, который исполняется, это назначение onmessage. Когда обработчик получает подходящее сообщение, он запускает функцию countFromZero, которая в ответ отправляет результаты своей работы. Другими словами, настройка рабочего процесса означает конверсию вызовов методов и результатов в сообщения.

    Вызов данного метода из приложения теперь выглядит так:

    var worker = new Worker('worker_count.js');
    worker.onmessage = function (e) { //e.data.sum это результат }
    
    //Вызов метода
    worker.postMessage({ method: "countFromZero", max: 1000, increment: .00005 });

    Имейте в виду, что сообщения postMessage – это асинхронные операции, таким образом, нет конкретных гарантий того, как быстро эти сообщения будут доставлены рабочему процессу или приложению. Более того, когда рабочий процесс создан, он не может приступить к работе до тех пор, пока исполняется скрипт (как когда вы вызываете setImmediate). Это означает, что рабочие процессы не особенно хорошо подходят для асинхронных операций, которые вы хотите запустить так быстро, как только возможно и от которых вы хотите получить результат, как только он будет готов. По этой причине, рабочие процессы лучше подходят для сравнительно больших операций и выполнения текущей работы. Маленькие, с высокой скоростью отклика, высокопроизводительные подпрограммы лучше размещать в WinRT-компонентах.

    Механизм postMessage так же не лучшим образом подходит для объединения нескольких асинхронных операций в цепочку, что мы легко можем реализовать с помощьюpromise-объектов, которые поступают из API WinRT. Честно говоря, я не хочу даже начинать думать о подобном коде! Я предпочитаю вместо этого задаваться вопросом о том, есть ли способ, с помощью которого мы можем эффективно заключить механизм сообщений рабочего процесса в оболочку promise-объекта, таким образом мы сможем обращаться с асинхронными операциями одинаково, несмотря на особенности их реализации.

    Чтобы это сделать, нам нужно найти способ получения результата из обработчика worker.onmessage и передачи его в обработчик завершения promise-объекта. Для того, чтобы это реализовать, мы используем некоторый код в главном приложении, который, на самом деле, выполняет ту же функцию, что и уровень проекции JavaScript, используемый для превращения WinRT API в promise-объекты

    // Это переменная функции, которую мы подключаем. 
    var workerCompleteDispatcher = null;
    
    var promiseJS = new WinJS.Promise(function (completeDispatcher, errorDispatcher,
    progressDispatcher) {	
    workerCompleteDispatcher = completeDispatcher;	
    });	
    
    // Здесь рабочий процесс должен быть создан и сохранен в переменной 'worker' variable
    // Прослушивание событий рабочего процесса
    worker.onmessage = function (e) {	
    if (workerCompleteDispatcher != null) {	
    workerCompleteDispatcher(e.data.sum);
    }	
    }	
    promiseJS.done(function (sum) {
    // Вывод данных рабочего процесса JavaScript 
    });

    Первое, что здесь нужно понять, это то, что, на самом деле, выполняет promise-объект, и как promise-объект отделен от асинхронной операции. (Так и должно быть, так как API и компоненты WinRT ничего не знают о promise-объектах). На самом деле, promise-объект – это лишь инструмент для управления множеством функций-прослушивателей от имени асинхронной операции, такой, как наш рабочий процесс. То есть, когда асинхронная операция обнаруживает определенные события, а именно, события завершения, ошибки, прогресса операции, она хочет сообщить об этом тому, кто выразил интерес к этим событиям. Этот кто-то делает это путем вызова методов then или done promise-объектов и предоставляя один или большее количество обработчиков.

    В методах then или done, все, что реально делают promise-объекты, это сохраняют указанные функции в списке (если только они не знают, что асинхронная операция уже завершана, в подобном случае они могут просто вызывать функцию обработки завершения или ошибки немедленном). Именно поэтому вы можете вызвать then или done много раз для того же самого promise-объекта – такие вызовы просто добавляют ваши обработчики завершения, ошибки и прогресса операции в соответствующий список внутри promise-объекта. Конечно, эти списки бесполезны, если нет какого-то способа вызвать обработчики, которые в них хранятся. Для этой цели у promise-объекта есть три собственных функции, которые просматривают каждый список и вызывают зарегистрированные прослушиватели. На самом деле, это – основная цель promise-объекта: управлять списками прослушивателей и вызывать эти прослушиватели тогда, когда будет нужно.

    Код, который запускает асинхронную операцию, таким образом, будет использовать promise-объект для управления этими прослушивателями, отсюда и вызов new WinJS.Promise. Но так же нужно получить доступ к этим функциям в promise-объекте, которые следует вызвать для уведомления их прослушивателей. Это – цель анонимной функции, предоставленной конструктору promise-объекта. Когда promise-объект инициализирован, эти функции вызываются со ссылками на функции, которые оповещают прослушиватели. Код асинхронной операции затем сохраняет все, что нужно, для более позднего использования. В нашем случае с рабочим процессом, мы заинтересованы лишь в оповещении обработчика завершения, поэтому мы сохраняем ссылку на подходящую функцию в переменной workerCompleteDispatcher.

    Когда затем мы обнаруживаем, что операция завершена – когда мы получаем соответствующее сообщение от рабочего процесса – мы проверяем, подходящая ли ссылка записана в workerCompleteDispatcher и затем вызываем ее с результирующим значением. Диспетчер, снова, перебирает все зарегистрированные прослушиватели и вызывает их с тем же результатом. В вышеприведенном коде единственный подобный прослушиватель – это анонимная функция, которую мы передали в promiseJS.done.

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

    Асинхронная операция (Async Operation)

    Promise-объект (Promise)

    Инициализация (initialize)

    Регистрация прослушивателей (register listeners)

    Приложение (Клиент) (App (Client))

    (рис 14.1) Promise-объект управляет прослушивателями и вызывает их от имени асинхронной операции

    Снова, все, что вы видите здесь, за исключением вызова done (который является клиентским кодом, а не частью асинхронной операции), это то, что делает уровень проекции JavaScript для асинхронных операций, поступающих из WinRT. В этих случаях асинхронная операция представлена объектом с интерфейсом IAsync*, а не рабочим процессом. Вместо прослушивания события message рабочего процесса, уровень проекции просто подключает себя через интерфейс IAsync* и создает promise-объект для управления соединениями из приложения.

    Вышеприведенный код включен в упражнение "Image Manipulation" в дополнительных материалах к лекции. Полезно в учебных целях установить точки останова на все анонимные функции и пошагово исполнить код для того, чтобы точно увидеть, где они вызываются, даже для того, чтобы пройти в WinJS для того, чтобы увидеть, как все это работает. В конце концов, очень важно то, что этот код предоставляет нам promise-объект (в promiseJS), который выглядит и работает точно так же как любой другой promise-объект. Это становится очень удобным, когда у нас есть promise-объекты от других асинхронных операций, как описано позже, во врезке "Объединение promise-объектов". Это означает, что мы можем смешивать и сочетать асинхронные операции из API WinRT, из компонентов WinRT и рабочих процессов.

    Основы асинхронности в WinRT-компонентах

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

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

    Во-вторых, возвращаемое значение метода должно быть одним из следующих интерфейсов (http://msdn.microsoft.com/library/windows/apps/br211924.aspx), показанных в таблице ниже, каждый из которых представляет определенноую комбинацию из асинхронных способов поведения, а именно, того, предоставляет ли метод результат и может ли метод сообщать о прогрессе хода операции.

    Интерфейс (в Windows.Foundation) Вариант использования
    IAsyncAction Используется для асинхронного метода, который не возвращает результатов (никакие аргументы не отправляются обработчику завершения) и не сообщает о ходе выполнения операции.
    IAsyncActionWithProgress<TProgress> Используется для асинхронного метода, который не возвращает результатов, но сообщает о ходе выполнения операции, где <TProgress> это тип данных аргумента, который передается в обработчик прогресса операции.
    IAsyncOperation<TResult> Используется для асинхронного метода, который возвращает результаты типа <TResult>, но не сообщает о ходе выполнения операции.
    IAsyncOperationWithProgress<TResult, TProgress=""> Используется для асинхронного метода, который возвращает результат типа <TResult> и сообщает о ходе выолнения операции с помощью аргумента типа <TProgress> обработчику прогресса.

    Выбрав тип асинхронного метода, который мы создаем, теперь мы можем исполнять код метода в другом потоке. Здесь можно использовать потоки напрямую, использовать пул потоков, предоставляемый ), однако, имеются и более высокоуровневые конструкции и в C#/VB и в C++, которые значительно упрощают работу.

    Асинхронные методы в C# и Visual Basic

    В C# и Visual Basic имеется класс ) для данной цели. Объект Task, создается с помощью одного из статических методов Task.Run. Мы передаем этому механизму анонимную функцию (она называется делегатом в .NET, определяется с помощью лямбда-выражения =>), которая содержит код для выполнения. Для того, чтобы потом конвертировать объект Task в подходящий асинхронный интерфейс WinRT, мы вызываем методы расширения задачи AsAsyncAction or AsAsyncOperation. Вот как это выглядит:

    public IAsyncOperation<string>
      SomeMethodAsync(int id)
      {
      var task = Task.Run<string>
        ( () => //	() => в C# это то же, что function () в JS
        {
        return "Here is a string.";
        });
        return task.AsAsyncOperation();
        }

    Внутри кода задачи производятся любые асинхронные операции (для чего мы используем ключевое слово C# await, как описано в вышеупомянутых сообщениях из блога), делегат должен быть маркирован с помощью async:

    public IAsyncOperation<string>
      SomeMethodAsync(int id)
      {
      var task = Task.Run<string>
        (async () =>
        {
        var idString = await GetMyStringAsync(id); //await делает асинхронный вызов выглядящим как синхронный
        return idString;
        });
        return task.AsAsyncOperation();
        }

    Обратите внимание на то, что ) и один из его методов Run, который подходит под нужное асинхронное поведение. Вызов Task.AsAsyncOperation в конце не нужен здесь, так как AsyncInfo.Run уже предоставляет соответствующий интерфейс:

    public IAsyncOperation<string>
      SomeMethodAsync(int id)
      {
      return AsyncInfo.Run<string>
        (async (token) =>
        {
        var idString = await GetMyStringAsync(id);
        token.ThrowIfCancellationRequested();
        return idString;
        });
        }

    В этом коде ). Для поддержки возможности отмены оерации, вы должны периодически вызывать метод маркера отмены ThrowIfCancellationRequested. Это позволяет распознать ситуацию, когда механизм, вызывавший асинхронный метод, отменил его (например, вызовом метода promise-объекта cancel). Так как отмена выполнения операции обычно инициируется пользователем, нет необходимости вызывать ThrowIfCancellationRequested его слишком часто. Если вызывать его каждые 50 миллисекунд или около того, приложение будет отлично реагировать на действия пользователя.

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

    Обратите внимание на то, что WinRT-методы могут принимать маркеры отмены из-за перегрузки AsTask. Вместо этого:

      await SomeWinRTMethodAsync();

    Вы можете использовать это:

     await SomeWinRTMethodAsync().AsTask(token);

    Во всяком случае, учитывая эти примеры, вот асинхронная версия CountFromZero, которая не поддерживает отмену:

     public static IAsyncOperation<double>
    CountFromZeroAsync(double max, double increment)
    {
    var task = Task.Run<double>
      (() =>
      {
      double sum = 0;
      for (double x = 0; x < max; x += increment)
    {
    sum += x;
    }
    return sum;
    }); 
    
    return task.AsAsyncOperation();
    }

    Интерфейс IAsyncOperation, возвращаемый этим методом, как и все асинхронные интерфейсты в Windows.Foundation, проецируется в JavaScript как promise-объект, таким образом, мы можем использовать обычный код для вызова методов и получения результатов (asyncVars это объект для хранения переменных):

    asyncVars.startCS = new Date();	
    var promiseCS = PixelCruncherCS.Tests.countFromZeroAsync(max, increment);
    promiseCS.done(function (sum) {	
    asyncVars.timeCS = new Date() - asyncVars.startCS;	
    asyncVars.sumCS = sum;	
    });

    Используя подобный код в упражнении "Image Manipulation" вы можете начать асинхронную операцию счета (использовав кнопку Counting Perf (Async)) и немедленно после этого открыть изображение и выполнить конверсию в оттенки серого в то же самое время.

    Асинхронные методы в C++

    Для реализации асинхронных методов в C++, нам нужно прийти к тому же конечному результату, что и в C# - к методу, который возвращает один из интерфейсов IAsync* и исполняет собственный внутренний код в другом потоке.

    Первая часть задачи весьма очевидна. Нам лишь нужно объявить метод с использованием типов C++ (здесь это показано в C++ коде; объявление класса в Grayscale.h выглядит точно так же):

    using namespace Windows::Foundation;
            IAsyncOperation<double>
              ^ Tests::CountFromZeroAsync(double max, double increment)

    Аналог класса ), его можно найти в том, что называется Среда выполнения с параллелизмом для C++ (Parallel Patterns Library for C++, PPL) или Concurrency Runtime, ее пространство имен – concurrency (http://msdn.microsoft.com/library/windows/apps/dd492819.aspx) (используйте #include <ppltasks.h> и using namespace concurrency; вы можете так поступить в коде C++). Функция, которая создает задачу, называется create_async. Все, что нам нужно – это обернуть наш код в такую функцию, как показано ниже:

    IAsyncOperation<double>
      ^ Tests::CountFromZeroAsync(double max, double increment)
      {
      return create_async([max, increment]()
      {
      double sum = 0;
      for (double x = 0; x < max; x += increment)
    {
    sum += x;
    }
    return sum;
    });
    }

    Как и в C#, здесь есть дополнительные структуры, предназначенные для организации вложенных асинхронных операций, поддерживающие отмену операции и сообщения о ходе выполнения операции. Подробности вы можете найти в материале документации "Асинхронное программирование на языке C++" (http://msdn.microsoft.com/library/windows/apps/hh780559.aspx) и "Параллелизм задач" (http://msdn.microsoft.com/library/windows/apps/dd492427.aspx).

    Врезка: Объединение отложенных результатов

    Есть одна особенность упражнения "Image Manipulation", которая использует преимущества того, что управление всеми асинхронными операциями реализуется с помощью promise-объектов. В приложении мы отображает горизонтальный индикатор выполнения перед началом всех асинхронных операций по кнопке Counting Perf (Async):

    function testPerfAsync() {
    showProgress("progressAsync", true);
    //...
    }

    Мы хотим, чтобы этот элемент управления оставался видимым во время выполнения любых асинхронных операций. Это легко можно сделать с помощью WinJS.Promise.join. Что здесь интересно отметить, этот то, что мы уже можем вызывать методы then или done у этих отдельных promise-объектов, что просто означает, что ы соединили различные обработчики для этих отдельных операций. Обработчики, которые мы передаем join, лишь привязаны к исполнению всех запрошенных отложенных операций:

    promiseJS.done(function (sum) {
    // Вывод данных для рабочего процесса JS
    }
    
    promiseCS.done(function (sum) {
    // Вывод данных для компонента C#
    })
    
    promiseCPP.done(function (sum) {
    // Вывод данных для компонента C++
    });
    
    WinJS.Promise.join([promiseJS, promiseCS, promiseCPP]).done(function () {
    //	Скрыть индикатор выполнения когда все операции будут выполнены
    showProgress("progressAsync", false);
    });

    В этом коде мы можем видеть, насколько все упрощается благодаря заключению механизма сообщений рабочего процесса внутрь promise-объекта. Не сделав этго, мы нуждались бы в управлении одним флагом для указания на то, получены ли отложенные результаты (устанавливая его в true внутри join), и другим флагом для того, чтобы показать, что получен результат от рабочего процесса (устанавливая его в true внутри обработчика сообщений рабочего процесса). Внутри join нам понадобилось бы проверять, завершена ли операция рабочего процесса, прежде чем скрыть индикатор выполнения. Обработчик сообщения рабочего процесса дела бы то же самое, проверяя, завершены ли операции в join. Подобными взаимоотношениями можно управлять в маленьких масштабах, но с множеством параллельных асинхронных операций все станет слишком запутанным – и именно в этом причина создания promise-объекта в первом случае!

    Массивы, векторы и другие альтернативы

    Теперь, когда мы рассмотрели базовую структуру асинхронных методов в WinRT-компонентах, посмотрим, как мы можем создать асинхронный вариант синхронного метода Convert, который мы реализовали ранее. Для целей этого упражнения мы будем придерживаться C#-компонента.

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

    Слудующее, что мы можем попробовать, это превратить асинхронное действие в операцию, которая производит результат. Мы можем решить выбрать возвращаемый тип IAsyncOperation<Byte[]> (или эквивалент, возвращающий еще и данные о прогрессе операции), где метод создаст и заполнит массив для возврата. Проблема здесь, тем не менее, в том, что приложение , получившее массив, не знает, как получить к нему доступ. Очевидно, некоторая память была выделена для массива, но выделение произошло внутри компонента, а не внутри JavaScript, и в данной ситуации нет четких правил того, как следует поступать. Так как это верный путь для утечек памяти, возврат подобных массивов не поддерживается.

    Альтернатива для асинхронного метода заключается в возврате специального типа коллекции WinRT (для которого применены четкие правила освобождения памяти), такого, как IList<Byte> , который будет в JavaScript конвертирован в вектор, к которому, кроме того, можно получить доступ как к массиву. (Обратите внимание на то, что тип IList специфичен для .NET-языков; пошаговый пример по C++ показывает, как использовать вектора напрямую с помощью типа concurrent_vector.) Вот простой пример подобного метода:

     public static IAsyncOperation<IList<Byte>
       > CreateByteListAsync(int size)
       {
       var task = Task.Run<IList<Byte>
         >(() =>
         {
         Byte [] list = new Byte[size];
    
         for (int i = 0; i < size; i++)
    {
    list[i] = (Byte)(i % 256);
    }
    return list.ToList();
    });
    return task.AsAsyncOperation();
    }

    Применение этого подхода к процедуре для преобразования в оттенки серого, мы получаем ConvertPixelArrayAsync (смотрите PixelCruncherCS > ConvertGrayscale.cs), где DoGrayscale это основной код процедуры, разбитый на различные функции, третий параметр – это периодически вызываемая функция обратного вызова, которую мы можем использовать для обработки отмены:

     
    public IAsyncOperation<IList<Byte>
           > ConvertPixelArrayAsync([ReadOnlyArray()] Byte[] imageDataIn)
           {
           //Используем AsyncInfo для создания IAsyncOperation которая поддерживает отмену
           return AsyncInfo.Run<IList<Byte>
    >((token) => Task.Run<IList<Byte>
      >(() =>
      {
      Byte[] imageDataOut = new Byte[imageDataIn.Length]; DoGrayscale(imageDataIn, imageDataOut, () =>
      {
      token.ThrowIfCancellationRequested();
      });
      return imageDataOut.ToList();
      }, token));
      }

    Четвертый подход заключается в использовании шаблона, который применяется классом Windows.Graphics.Imaging.PixelDataProvider, который мы уже используем в упражнении "Image Manipulation". В функции setGrayscale (js/default.js), мы открываем файл, полученный от средства выбора файлов, и затем декодируем его с помощью BitmapDecoder.getPixelDataAsync. Результат этой операции – это PixelDataProvider, у которого есть метод detachPixelData который предоставляет нам пиксельный массив (некоторые участки кода опущены для краткости):

    function setGrayscale(componentType) {
    imageFile.openReadAsync().then(function (stream) {
    return Imaging.BitmapDecoder.createAsync(stream);
    }).then(function (decoderArg) {
    //Конфиругирование декодера ... [код опущен]
    return decoder.getPixelDataAsync();
    }).done(function (pixelProvider) {
    copyGrayscaleToCanvas(pixelProvider.detachPixelData(),
    decoder.pixelWidth, decoder.pixelHeight, componentType);
    });
          

    Похожая реализация нашей процедуры конвертации в оттенки серого, находится в PixelCruncherCS > ConvertGrayscale.cs, в функции ConvertArraysAsync. Она имеет тип IAsyncAction , так как она работает с массивом Grayscale.inputData (который следует сначала установить). Выходные данные доступны из Grayscale.detatchOutputData(). Вот, как выглядит код JavaScript:

    pc1.inputData = pixels;
    pc1.convertArraysAsync().done(function () {
    var data = pc1.detachOutputData()
    copyArrayToImgData(data, imgData);
    updateOutput(ctx, imgData, start);
    });

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

    В течение всего примера мы загружали данные изображения из файла, используя BitmapDecoder, и затем конвертируем полученные пиксели в оттенки серого в массиве, полученном с помощью метода createImageData элемента canvas. Как только данные окажутся внутри объекта данных изображения, мы можем вызвать метод putImageData элемента canvas для их вывода на экран. Все это было изначально реализовано для показа взаимодействия с этим элементом, включая сохранение его содержимого в файл. Это было нормально для Главы 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", где нашей основной темой была работа с графикой. Однако, если нам нужна лишь конверсия файла изображения в оттенки серого, использование элемента canvas – это не лучший подход.

    Ключевая проблема, с которой мы здесь столкнулись, заключается в том, что метод putImageData этого элемента принимает только объект ImageData, созданный методом createImageData все того же элемента canvas. Элемент не позволяет вам создать и вывести отдельный пиксельный массив, не позволяет и вставить произвольный массив в свойство ImageData.data. Единственный способ, которым можно воспользоваться, это прямая запись данных в массив ImageData.data.

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

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

    Возвращаясь назад, можно вспомнить, что общая цель демонстрации была в конвертации файла изображения в оттенки серого и показе результатов на экране. Использование элемента canvas – это лишь деталь реализации – мы можем достигнуть тех же результатов другим путем. Например, вместо конверсии пикселей в массиве в памяти, мы можем создать временный файл, используя вместо этого класс Windows.Graphics.Image.BitmapEncoder, так же, как мы использовали его в функции SaveGrayscale, которая уже есть в приложении. Мы просто передали ей массив сконвертированных пикселей, вместо того, чтобы брать эти пиксели снова из элемента canvas. Тогда мы можем использовать URL.createObjectURL или URI ms-appdata:/// для отображения его в элементе img. Подобное будет производиться гораздо быстрее, так как метод putImageData элемента canvas требует много времени для выполнения, гораздо больше, чем занимают процедуры конверсии в наших компонентах.

    В том же духе, нет особой причины, по которой мы не могли бы разместить весь этот процесс внутри компонента. Только та часть, которая работает с пользовательским интерфейсом, должна быть в JavaScript, но все остальное может быть написано на другом языке. Например, зачем беспокоить себя передачей пиксельного массива между JavaScript и WinRT-компонентом? Как только мы получили исходный StorageFile из средства выбора файлов, мы можем передать его напрямую в компонент. Затем он может использовать BitmapDecoder для получения пиксельноого потока, конвертировать его, затем создать временный файл и записать конвертированные пиксели обратно, используя BitmapEncoder, передать обратно объект StorageFile для временного файла, на основе которого мы можем установить img src. Таким образом, пиксельные данные никаогда не покидают компонент и никогда не копируются между буферами в памяти. Это должно привести и к более высокой производительности и к меньшему потреблению памяти.

    Для этого в проекте PixelCruncherCS в упражнении "Image Manipulation" есть еще один асинхронный метод, который называется ConvertGrayscalFileAsync и выполняет в точности то, о чем я сказал выше:

    public static IAsyncOperation<StorageFile>
       ConvertGrayscaleFileAsync(StorageFile file)
          {
      return AsyncInfo.Run<StorageFile>
      ((token) => Task.Run<StorageFile>(async () =>	
    {	
    StorageFile fileOut = null;	
    try
    {
    //Открыть файл и прочесть пиксельные данные
    using (IRandomAccessStream stream = await file.OpenReadAsync())
    {
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream); PixelDataProvider pp = await decoder.GetPixelDataAsync();
    Byte[] pixels = pp.DetachPixelData();
    
    //Мы знаем, что наш метод может произвести конверсию там же, где находятся данные
    //поэтому нам не нужно создавать копию данных
    DoGrayscale(pixels, pixels);
    
    //Сохранить временный файл.
    ApplicationData appdata = ApplicationData.Current;
    
    fileOut = await appdata.TemporaryFolder.CreateFileAsync
    ( "ImageManipulation_GrayscaleConversion.png", CreationCollisionOption.ReplaceExisting);
    
    using (IRandomAccessStream streamOut =	
    await fileOut.OpenAsync(FileAccessMode.ReadWrite))	
    {	
    BitmapEncoder encoder = await BitmapEncoder.CreateAsync(
    BitmapEncoder.PngEncoderId, streamOut);	
    
    encoder.SetPixelData(decoder.BitmapPixelFormat, decoder.BitmapAlphaMode,
     decoder.PixelWidth, decoder.PixelHeight,
    decoder.DpiX, decoder.DpiY, pixels);
    
    await encoder.FlushAsync();
    }
    }
    }
    catch
    {
    //Произошла ошибка; очистим fileOut 
    fileOut = null;
    }
    
    //Наконец, возвращаем созданный StorageFile, что делает удобным для вызывающей процедуры	
    //скопировать его куда угодно, использовать в вызове наподобие URL.createObjectURL, или сослаться
    //на него с помощью конструкции "ms-appdata:///temp" + fileOut.Name	
    
    
    return fileOut;	
    }));	
    }	

    В этом коде, в сравнении с эквивалентным кодом на JavaScript мы можем видеть, что ключевое слово await в C# очень упрощает работу с асинхронными методами – они выглядят так, как будто являются синхронными. Это одно из потенциальных преимуществ написания кода в компоненте! Другая важная деталь, на которую нужно обратить внимание, это использование операций с потоками. Потоки, в сравнении с другими типами, являются высвобождаемыми (disposable) (у них есть интерфейс IDisposable) и должны быть очищены после использования, иначе файлы остаются открытыми и вы можете столкнуться с исключением отказа в доступе или с другим странным поведением. Выражение using самостоятельно реализует подобную логику очистки.

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

    PixelCruncherCS.Grayscale.convertGrayscaleFileAsync(imageFile).done(function (tempFile) {
    if (tempFile != null) {	
    document.getElementById("image2").src = "ms-appdata:///temp/" + tempFile.name;	
    }	
    });	

    Строки с URI можно так же заменить в этом случае:

    var uri = URL.createObjectURL(tempFile, { oneTimeOnly: true });
    document.getElementById("image2").src = uri;

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

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

    Проекции в JavaScript

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

    Начнем с именования. Мы уже видели, что в проекты приложений на JavaScript компоненты добавляются в виде ссылок, после чего пространство имен компонента становится доступным в JavaScript, других объявлений не требуется. Пространство имен и классы в компоненте напрямую доступны в JavaScript. Однако, меняются имена методов, свойств и событий. Хотя пространство имен и имена классов проецируются в том виде, в котором они существуют в компоненте, имена методов и свойств (в том числе – члены структур (struct) и перечислений (enum)) конвертируются с использованием "верблюжьего" стиля: имена TestMethod и TestProperty в компоненте принимают вид testMethod и testProperty в JavaScript. Подобное изменение регистра символов может привести к необычному эффекту, как когда имя компонента начинается с двух заглавных букв, как, например, когда UIProperty, видно в JavaScript как uIProperty.

    С другой стороны, имена событий приводятся к использованию только прописных букв, что соответствует соглашению об именовании, принятому в JavaScript. Событие, имеющее имя SignificantValueChanged в компоненте превращается в significantvaluechanged в JavaScript. Это имя, в нижнем регистре, используется с addEventListener, и классу, которые его предоставляет, так же будет задано свойство, имя которого состоит из данного имени с префиксом on, как, например, onsignificantvaluechanged. Важная особенность в работе с событиями заключается в том, что иногда нужно явным образом вызывать removeEventListener для предотвращения утечек памяти. Подробнее об этом – в Главе 3 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript". В контексте данной лекции, подобное применимо и к вашим собственным WinRT-компонентам.

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

    Вот два ограничения, которые мы упоминали ранее, и которые полезно будет повторить сейчас. Первое – это то, что WinRT-компоненты не могут работать с пользовательским интерфейсом приложения, написанного на JavaScript. Это так, потому что приложение не может получить поверхность для вывода любого рода, которую может использовать компонент. Второе – это то, что JavaScript может различать перегруженные методы лишь по их арности (количеству параметров), а не по типу. Если компонент предлагает перегрузки, отличающиеся лишь по типу, JavaScript может получить доступ только к тем из них, которые помечены как варианты по умолчанию.

    Далее, мы приходим к вопросу о типах данных, что всегда интересно, когда речь идет о взаимодействии между разными языками. Вообще говоря, то, что вы видите в JavaScript, обычно соответствует тому, что есть в компоненте. Тип WinRT DateTime становится типом JavaScript Date, числовые значения становятся типом Number, bool превращается в Boolean, строки остаются строками и так далее. Некоторые типы WinRT, наподобие IMapView и IPropertySet, просто переходят в JavaScript напрямую как объектные типы, так как у них нет внутренних эквивалентов в нем. Вот еще некоторые, более интересные, преобразования:

  • Асинхронные операции в компоненте, которые возвращают интерфейс наподобие IAsyncOperation, проецируются в JavaScript как promise-объекты.
  • Так как JavaScript не имеет концепции struct, которая есть в C#, VB, и C++, структуры из WinRT-компонентов появляются в JavaScript как объекты с полями структуры в качестве членов. Похожим образом, для вызова WinRT-компонента, который принимает аргумент struct, JavaScript-приложение создает объект с полями, представленными членами и передает этот объект вместо структуры. Обратите внимание, что имена членов struct в JavaScript конвертируются с использованием "верблюжьего" стиля.
  • Некоторые типы коллекций, таких, как IVector, появляеются в JavaScript как массивы, но с различными методами. Таким образом, доступ к коллекции можно получить с помощью оператора массива [ ], но у такого массива будут другие методы. Будьте осторожны, таким образом, передавая эти массивы функциям манипуляции JavaScript, которые подразумевают наличие у массивов стандартных методов.
  • Перечисления преобразовываются в объекты с использованием свойств, соответствующих значениям перечисления, имена которых преобразуются к "верблюжьему" стилю, значения приводятся к JavaScript-типу Number.
  • API WinRT иногда возвращают типы Int64 (в виде отдельных значений или в structs), для которых нет эквивалентов в JavaScript. Однако, 64-битные типы в JavaScript сохраняются, поэтому вы можете передать их в WinRT при другом вызове. Однако, если вы модифицируете переменную, хранящую подобное значение, даже с использованием чего-то простого, вроде оператора ++, она будет конвертирована в тип JavaScript Number. Подобое значение не будет принтято методами, ожидающими Int64.
  • Если метод компонента предоставляет несколько выходных параметров, они видны в JavaScript как объект с этими различными значениями. Четкого стандартна для подобного преобразования в JavaScript нет, поэтому лучше стараться не создавать компоненты, имеющие подобную структуру.
  • Суть заключается в том, что уровень проекции пытается сделать WinRT-компоненты, написанные на любом другом языке выглядящими и работающими так, как будто они принадлежат JavaScript, и использование которых не будет вызывать сложностей..

    Сценарии для WinRT-компонентов

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

    Повышение производительности

    Увеличение производительности приложений для Магазина Windows, написанных на HTML, CSS и JavaScript, это один из основных сценариев передачи некоторой части работы на WinRT-компонентам.

    Оценивая производительность приложения, обратите особое внимание на те области, где задействованы интенсивные вычисления или передача больших объемов данных. Например, если вы решаете реализовать расширенный экран-заставку, возможно, вы сможете уменьшить время, которое должен ждать пользователь (особенно – при первом запуске) до тех пор, пока приложение не активируется. Любая другая ситуация, где пользователю может понадобиться ждать – там, где у него есть возможность делать что-то более полезное, чем наблюдение за индикатором прогресса – это хорошее место для использования, если это возможно, высокопроизводительных компонентов. Очевидно, есть сценарии, где сама по себе производительность приложения не так влияет на скорость работы с ним, как сетевые задержки, но как только вы получили данные от некоего сервиса, вы быстрее можете произвести их предварительную обработку в компоненте, а не в JavaScript.

    Другое хорошее место для использования высокопроизводительных компонентов – это действия, выполняемые при запуске приложения, особенно если при первом запуске требуется много работы. Например, пакет приложения может включать в себя большой объем сжатых данных для уменьшения размера данных, загружаемых из Магазина Windows, но эти данные нужно распаковать при первом запуске. WinRT-компонент может значительно сократить время инициализации. Если компонент использует API WinRT для записи данных в папки приложения, все эти данные так же доступны и из JavaScript посредством тех же самых API.

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

    Одно из мест, где очень важна производительность, это фоновые задачи. Как разьяснено в Главе 2, фоновые задачи ограничены несколькими секундами процессорного времени в каждые 15 минут. Поэтому вы сможете сделать гораздо больше в фоновой задаче, написанной на высокопроизводительном языке, нежели в задаче, написанной на JavaScript.

    Структура компонента с подобной задачей не отличается от любого другого компонента, как показано в компоненте " C# Tasks" в примере "Фоновые задачи" (http://code.msdn.microsoft.com/windowsapps/Background-Task-Sample-9209ade9). Каждый из классов в пространстве имен Tasks маркирован как public и sealed, и так как компонент виден в JavaScript-проекте посредством ссылки, имена классов (и их открытые методы и свойствва), так же находятся в пространстве имен JavaScript. В результате их имена без проблем могут быть заданы свойству BackgroundTaskBuild.taskEntryPoint.

    Другой пример использования той же самой техники доступен в примере "Фоновое определение состояния сети" (http://code.msdn.microsoft.com/windowsapps/Network-status-background-957eb3eb).

    Кое что, о чем мы не говорили в Главе 2, но о чем самое время поговорить теперь, это то, что при создании WinRT-компонента для данной задачи, класс, который реализует фоновую задачу, должен быть унаследован от ) и реализовывать его метод Run. Этот метод вызывается при активации фоновой задачи. Это можно увидеть в примере "Фоновое определение состояния сети", где вся C#-реализация компонента заключена в нескольких десятках строк кода (проект NetworkStatusTask > BackgroundTask.cs; некоторые комментарии и команды тестового вывода опущены):

    namespace NetworkStatusTask
    {
    public sealed class NetworkStatusBackgroundTask : IBackgroundTask
    {
    ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
    
    // Метод Run – это точка входа для фоновой задачи.
    public void Run(IBackgroundTaskInstance taskInstance)
    {
    // Связывание обработчика отмены операции с фоновой задачей.
    taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
    try
    {
    ConnectionProfile profile =
    NetworkInformation.GetInternetConnectionProfile();
    if (profile == null)
    {
    localSettings.Values["InternetProfile"] = "Not connected to Internet";
    localSettings.Values["NetworkAdapterId"] = "Not connected to Internet";
    }
    else
    {
    localSettings.Values["InternetProfile"] = profile.ProfileName;
    
    var networkAdapterInfo = profile.NetworkAdapter;
    if (networkAdapterInfo == null)
    {
    localSettings.Values["NetworkAdapterId"] = "Not connected to Internet";
    }
    else
    {
    localSettings.Values["NetworkAdapterId"] =
    networkAdapterInfo.NetworkAdapterId.ToString();
    }
    }
    }
    catch (Exception e)
    {
    // Тестовый вывод опущен
    }
    }
    
    // Обработка отмены фоновой задачи.
    private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
    // Тестовый вывод опущен
    }
    }
    }

    Вы можете видеть, что метод Run принимает аргумент, с помощью которого он может зарегистрировать обработчик для отмены задачи. Вышеприведенный код ничего значимого не делает, так как задача исполняет лишь небольшое количество кода. Задачи C# в примере "Фоновые задачи", с другой стороны, имитируют длительные операции, в подобном случае используется обработчик для установки флага, который может остановить эти операции.

    Доступ к дополнительным API

    Среди API DOM, WinJS, библиотек сторонних разработчиков и внутренних возможностей JavaScript, у JavaScript-разработчиков нет недостатка в API для использования в своих приложениях. В то же время, есть огромное количество API .NET (http://msdn.microsoft.com/library/windows/apps/br230232.aspx) и Win32/COM (http://msdn.microsoft.com/library/windows/apps/br205757.aspx), которые доступны в приложениях на C#, VB, и C++ и не доступны напрямую в JavaScript, в том числе API DirectX b Media Foundation.

    За исключением API, которые воздействуют на пользовательский интерфейс или поверхности рисования (а именно, Direct2D и Direct3D), WinRT-компоненты могут делать подобные функции, или, скорее, высокоуровневые операции, построенные на их основе, доступными для приложений, написанных на JavaScript.

    В материале "Создание собственных компонентов среды выполнения Windows для разработки эффективных приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/08/13/windows-metro.aspx) блога для разрабочтиков приложений для Windows 8, даны некоторые советы по этому поводу. Здесь показано, как использовать API System.IO.Compression в .NET для работы с ZIP-файлами, и API XAudio (часть DirectX) для обхода элемента HTML audio и низкоуровневого проигрывания аудиофайлов. В Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" мы касались этого вопроса, когда, как бы мы ни старались, между воспроизведением записей в элементе audio, всегда между ними была небольшая пауза. Это происходит потому, что некоторое время занимает перевод всех операций элемента в вызовы системного API XAudio. Работая напрямую с тем же самым API, вы можете полностью решить эту проблему (Тем не менее, Microsoft знает о подобном поведении элемента audio и, весьма вероятно, улучшит его производительность в будущем.)

    Подобный подход так же можно использовать для взаимодйствия с внешними устройствами, которые не представлены в API WinRT, но представлены в Win32/COM. Мы видели подобное с применением ), в Главе 4 (речь об этом идет и в вышеупомянутом материале блога).

    Другой очень простой пример – это создание компонента для ответа на частый вопрос: "Как создать GUID в JavaScript?". Хотя вы можете реализовать процедуру для создания GUID-строки из случайных чисел, это неподходящий GUID, так как нет гарантии его уникальности (в конце концов, GUID – это Глобальный уникальный идентификатор (Globally Unique Identifier). Для того, чтобы сделать все как нужно, используйте API Win32 CoGreatGuid (http://msdn.microsoft.com/library/windows/apps/ms688568.aspx), для чего вы можете создать очень простую оболочку на C++.

    "Из пушки по воробьям?" Некоторые разработчики говорят, что разбираться со всеми тонкостями создания WinRT-компонентов только для того, чтобы вызывать один метод наподобие CoCreateGuid – это слишком тяжеловесное решение. Однако, учитывая простоту реализации базового WinRT-компонента, которую мы видели в данной лекции, все, что вы действительно делаете с компонентом, это создаете многоязычную структуру, посредством которой вы можете использовать все возможности каждого из языков. Накладные расходы, на самом деле, невелики. Например, при Release-построении C++-компонента из раздела "Быстрый старт №2" получилась DLL объемом 39 Кб и 3-килобайтный .winmd-файл.

    Использование WinRT-компонентов, таким образом, применимо и к использованию COM DLL, которые содержат API сторонних разработчиков, наподобие библиотек кода. Вы можете использовать их в приложениях для Магазина Windows при условии, что они отвечают трем требованиям:

  • DLL находится в пакете приложения.
  • DLL использует только те API Win32/COM (http://msdn.microsoft.com/library/windows/apps/br205757.aspx), которые разрешено использовать с приложениями для Магазина Windows. В противном случае приложение не пройдет сертификацию в Магазине Windows.
  • DLL должна реализовывать то, что называется Regfree COM, то есть, для ее работы не должны требоваться какие-либо записи в системном реестре . (Приложения для Магазина Windows не имеют доступа к реестру, и, таким образом, не могут регистрировать там COM-библиотеки). Лучший материал на эту тему, который я нашел, это "Упрощение развертывания приложения с ClickOnce и Registration-Free COM" (http://msdn.microsoft.com/en-us/magazine/cc188708.aspx) в MSDN Magazine.
  • Если все эти требования соблюдены, приложение может использовать функцию ) из компонента для создания экземпляров объектов из нужной ему DLL.

    Скрытие кода и защита интеллектуальной собственности.

    В Главе 1 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript" мы видели, как загружаются и исполняются приложения. Сейчас, возможно, вы уже понимаете, что как старательно Windows ни пытается закрыть пакеты приложений от случайного доступа, весь код приложений находится на устройстве и пользователь может получить к нему доступ. Другими словами, знайте, что ваш код так же видим в пакете приложения, как если бы это был код страницы в веб-браузере, когда в нем выбирают команду Показать код

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

    Как только разработчики начали экспериментировать с приложениями для Магазина Windows , написанными на HTML, CSS и JavaScript, они задавали вопросы о том, как им защитить свой код. На самом деле, разработчики, использующие C# и Visual Basic задают похожие вопросы, хотя эти языки компилируются в IL (промежуточный язык, intermediate language), существует множество декомпиляторов, которые производят исходный код из IL, есть и программы, которые позволяют раскрывать минимизированный JavaScript-код. Ни JavaScript, ни .NET-языки не умеют достаточно хорошо скрывать подробности реализации приложений.

    Код, написанный на C++ и скомпилированный в машинный код, гораздо хуже поддается методам обратной инженерии, хотя и это задача вполне выполнимая для некоторых (в таком случае, вы можете спросить у них, почему бы им не писать собственный код!). Тем не менее, это лучшая защита для кода, который существует на клиентском компьютере. А единственный реальный способ защитить алгоритмы – это хранить их на удаленном сервере.

    Если остальное приложение написано на языке, отличном от C++, в особенности – на JavaScript, знайте, что довольно легко выяснить и особенности реализации интерфейса компонента. Вопрос здесь в том, может ли злоумышленник может использовать знание интерфейса компонента для использования этого компонента в его собственных приложениях. Короткий ответ: "Да, может", - так как ваш код покажет ему, как это сделать. В подобных случаях более гибкий и почти непробиваемый подход заключается в том, чтобы поставщик компонента в индивидуальном порядке управлял лицензиями разработчиков. У компонента может быть предусмотрена процедура инициализации, необходимая для включения остальной его функциональности. В этом вызове он может сравнить сведения о пакете приложения, полученные посредством класса Windows.ApplicationModel.PackageId с теми, которые известны ему, с учетом того, что уникальность параметров, идентифицирующих приложение, поддерживается Магазином Windows. Вот нескольуко вариантов проверки:

  • Провести проверку с помощью сетевого сервиса. Это может потребовать наличия сетевого соединения, что, в большинстве сценариев, не проблема. Помните лишь о шифровании данных, которые вы отправляете по сети для того, чтобы избежать их перехвата с помощью инструментов наподобие Fiddler!
  • Сверить данные с зашифрованной информацией, которая находится в самом компоненте. Таким образом, компонент компилируется отдельно для каждой лицензии. Подобный подход сложнее всего для взлома.
  • Произвести сверку с зашифрованным файлом лицензии, распространяющимся вместе с компонентом и уникальным для лицензиата (содержащим, например, имя приложения и издателя). Возможно, это самое простое решение, так как даже если файл лицензии скопирован из пакета лицензированного приложения, содержащаяся в нем информация не будет совпадать с информацией о пакете другого приложения во время его выполнения. Алгоритм шифрования может содержаться внутри скомпилированного компонента, поэтому их будет сложно извлечь для того, чтобы взломать файл с лицензией – не невозможно, но очень сложно. Другое приложение может использовать такой компонент, только если оно использует ту же самую информацию о пакете приложения, что невозможно для приложений, загруженных из Магазина Windows, но возможно для разработчиков, при самостоятельной работе с приложениями, или для недобросовестных организаций.
  • В итоге, таким образом, учитывайте, что Windows сама по себе не может гарантировать безопасность кода приложений на клиентском устройстве. Дальнейшая защита должна быть реализована приложением самостоятельно.

    Библиотеки компонентов

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

    Хороший пример библиотеки – это Notifications Extensions Library, которую мы видели в нескольких примерах, в Главе 2: "Плитки и индикаторы событий приложения" (http://code.msdn.microsoft.com/windowsapps/App-tiles-and-badges-sample-5fc49148), "Приложения экрана блокировки" (http://code.msdn.microsoft.com/windowsapps/Lock-screen-apps-sample-9843dc3a), "Запланированные обновления" (http://code.msdn.microsoft.com/windowsapps/Scheduled-notifications-da477093), "Дополнительные плитки" (http://code.msdn.microsoft.com/windowsapps/Secondary-Tiles-Sample-edf2a178) и "Всплывающие уведомления" (http://code.msdn.microsoft.com/windowsapps/toast-notifications-sample-52eeba29). Эта библиотека содержит множество классов с соответствующими методами и свойствами, и так как все эти методы маленькие и быстрые, они реализованы в синхронном виде.

    В случае с примерами, библиотека Notifications Extensions Library включена в каждый проект, который ее использует, в виде исходного кода. Более вероятно, однако, в особенности если вы создаете коммерческий проект и следуете руководству "Практическое руководство. Создание пакета средств разработки программного обеспечения" (http://msdn.microsoft.com/library/windows/apps/hh768146.aspx), вы будете передавать лишь скомпилированные DLL, и/или WinMD-файлы вашим клиентам. Клиенты будут добавлять эти библиотеки в свои проекты, в итоге, они будут упакованы вместе с приложениями.

    В подобном случае не забудьте предоставить отдельные компоненты, скомпилированные для целевых платформ x86, x64, и ARM для компонентов, написанных на C++.

    Параллелизм

    Мы уже видели, что рабочие веб-процессы и WinRT-компоненты с асинхронными методами могут работать бок о бок для передачи задач в различные потоки. Если вам действительно нужно подобное, вы можете спроектировать все приложение на основе параллельного выполнения, используя эти механизмы, запуская одновременно несколько асинхронных операций, возможно, с помощью нескольких рабочих процессов. Компоненты WinRT так же могут использовать множественные вызовы Task.Run, а не только отдельные, как мы видели.

    Если посмотреть глубже, то WinRT-компоненты могут использовать API в ) для получения доступа к пулу потоков, а так же те API в , которые работают с семафорами и с другими событиями, связанными с многопоточностью. Подробности подобной работы лежат за пределами данного курса, но я хочу упомянуть их здесь, так как многие встроенные API WinRT их использует, и ваши компоненты могут делать то же самое. И, хотя здесь не идет речь о компонентах, пример "Пул потоков" (http://code.msdn.microsoft.com/windowsapps/Pool-Sample-5aa60454) предоставляет ценную информацию для того, чтобы начать разбираться с этим вопросом.

    Что мы только что изучили

  • Приложения для Магазина Windows не обязательно писать на каком-то одном языке. С помощью WinRT-компонентов приложения могут эффективно использовать язык, который наилучшим образом подходит для решения конкретной задачи. Компоненты, однако, не могут работать с пользовательским интерфейсом от имени приложения, написанного на HTML, CSS и JavaScript.
  • Причины для применения нескольких языков в приложении включают в себя улучшение производительности, получение доступа к дополнительным API (включая библиотеки сторонних разработчиков), которые при обычном режиме не доступны JavaScript, скрытие кода для защиты интеллектуальной собственности, создание модульных библиотек компонентов, которые могут использовать приложения, написанные на других языках и эффективное распараллеливание задач.
  • Для решения задач, требующих интенсивных вычислений, компоненты, написанные на C#/VB позволяют достичь повышения производительности примерно на 15% в сравнении с JavaScript-кодом, а компоненты, написанные на C++, примерно на 25%. При тестировании производительности не забудьте выполнить Release-построение приложения и запустить приложение вне отладчика, в противном случае вы увидите другие результаты для разных языков.
  • Приложения для Магазина Windows могут использовать рабочие веб-процессы для создания асинхронных процедур, которые выполняются отдельно от потока пользовательского интерфейса, вы можете заключать эти рабочие процессы в оболочку promise-объектов WinJS для того, чтобы работать с ними так же, как с асинхронными методами WinRT.
  • Асинхронные методы так же могут быть реализованы в WinRT-компонентах с использованием задач, параллелизма и API пула потоков. В сравнении с рабочими веб-процессами, подобные асинхронные методы отличаются более высокой скоростью отзыва, так как их внутреннее устройство соответствует устройству методов.
  • Вне зависимости от языка, на котором написан компонент, уровень проекции JavaScript переводит некоторые из его структур в вид, естественный для JavaScript, включая изменение регистра имен и конверсию типов данных.
  • В связи с сущностью обработчиков событий, которые пересекают границу между JavaScript и WinRT-компонентами, приложения должны предупреждать утечку памяти, вызывая removeEventListener для событий, исходящих из API WinRT или WinRT-компонентов, когда прослушивают их лишь временно.
  • Вернуться к учебному плану