Поделиться через


Создание и использование службы приложений

Внимание

Список кода в этом разделе — это только C#. Пример приложения службы приложений в C++/WinRT , а также C#, см . в примере приложения службы приложений.

Службы приложений — это приложения UWP, которые могут предоставлять службы другим приложениям UWP. Это похоже на веб-службы на устройстве. Служба приложений выполняется как фоновая задача в приложении узла и предоставляет соответствующую службу другим приложениям. Например, служба приложений может предоставлять службу сканирования штрихкода, которую могут использовать другие приложения. Или корпоративный набор приложений может использовать общую службу проверки орфографии, которая доступна всем приложениям в наборе. Службы приложений позволяют создавать службы без пользовательского интерфейса, которые приложения могут вызывать на одном устройстве и, начиная с Windows 10 версии 1607, на удаленных устройствах.

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

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

В этой инструкции для простоты мы создадим универсальное решение.

  1. В Visual Studio 2015 или более поздней версии создайте проект приложения UWP и назовите его AppServiceProvider.

    1. > Выберите новый > проект...
    2. В диалоговом окне "Создание проекта" выберите пустое приложение (универсальная версия Windows) C#. Это приложение будет предоставлять службу приложения другим приложениям UWP.
    3. Нажмите кнопку "Далее", а затем назовите проект AppServiceProvider, выберите расположение для него и нажмите кнопку "Создать".
  2. Когда будет предложено выбрать целевую и минимальную версию проекта, выберите по крайней мере 10.0.14393. Если вы хотите использовать новый атрибут SupportsMultipleInstances , необходимо использовать Visual Studio 2017 или Visual Studio 2019 и нацелиться на 10.0.15063 (Windows 10 Creators Update) или более поздней версии.

Добавление расширения службы приложений в Package.appxmanifest

В проекте AppServiceProvider откройте файл Package.appxmanifest в текстовом редакторе:

  1. Щелкните его правой кнопкой мыши в Обозреватель решений.
  2. Нажмите кнопку "Открыть с".
  3. Выберите редактор XML (текст).

Добавьте следующее AppService <Application> расширение внутри элемента. В этом примере показано, как объявить службу com.microsoft.inventory и указать, что приложение является поставщиком службы приложений. Сама служба будет реализована в виде фоновой задачи. Проект приложения службы приложений предоставляет службу другим приложениям. Для имени службы мы рекомендуем использовать обратное доменное имя.

Обратите внимание, что префикс пространства имен и uap4:SupportsMultipleInstances атрибут допустимы только в том случае, xmlns:uap4 если вы используете windows SDK версии 10.0.15063 или более поздней. Их можно удалить, если используется более ранняя версия SDK.

Примечание.

Пример приложения службы приложений в C++/WinRT , а также C#, см . в примере приложения службы приложений.

<Package
    ...
    xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
    xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4"
    ...
    <Applications>
        <Application Id="AppServiceProvider.App"
          Executable="$targetnametoken$.exe"
          EntryPoint="AppServiceProvider.App">
          ...
          <Extensions>
            <uap:Extension Category="windows.appService" EntryPoint="MyAppService.Inventory">
              <uap3:AppService Name="com.microsoft.inventory" uap4:SupportsMultipleInstances="true"/>
            </uap:Extension>
          </Extensions>
          ...
        </Application>
    </Applications>

Атрибут Category определяет это приложение как поставщик службы приложений.

Атрибут EntryPoint определяет квалифицированный класс пространства имен, реализующий службу, которую мы реализуем далее.

Атрибут SupportsMultipleInstances указывает, что каждый раз, когда служба приложений вызывается, что она должна выполняться в новом процессе. Это не обязательно, но доступно для вас, если вам нужна эта функция и предназначена для пакета SDK 10.0.15063 (Windows 10 Creators Update) или более поздней версии. В качестве префикса следует использовать пространство имен uap4.

Создание службы приложений

  1. Служба приложений будет реализована в виде фоновой задачи. Это позволяет приложению переднего плана вызывать службу приложений в другом приложении. Чтобы создать службу приложений в качестве фоновой задачи, добавьте новый проект компонента среда выполнения Windows в решение (добавление > нового проекта файла>) с именем MyAppService. В диалоговом окне "Добавление нового проекта" выберите установленный > компонент Visual C# > среда выполнения Windows (универсальная версия Windows).

  2. В проекте AppServiceProvider добавьте ссылку на проект в проект MyAppServiceОбозреватель решений щелкните правой кнопкой мыши проект> AppServiceProvider Add>Reference>Projects>Solution, выберите MyAppService>OK). Этот шаг очень важен: если вы не добавите ссылку, служба приложения не сможет подключиться во время выполнения.

  3. В проекте MyAppService добавьте следующие инструкции using в начало Class1.cs:

    using Windows.ApplicationModel.AppService;
    using Windows.ApplicationModel.Background;
    using Windows.Foundation.Collections;
    
  4. Переименуйте Class1.cs на Inventory.cs и замените код заглушки для Класса1 новым классом фоновой задачи с именем Inventory:

    public sealed class Inventory : IBackgroundTask
    {
        private BackgroundTaskDeferral backgroundTaskDeferral;
        private AppServiceConnection appServiceconnection;
        private String[] inventoryItems = new string[] { "Robot vacuum", "Chair" };
        private double[] inventoryPrices = new double[] { 129.99, 88.99 };
    
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Get a deferral so that the service isn't terminated.
            this.backgroundTaskDeferral = taskInstance.GetDeferral();
    
            // Associate a cancellation handler with the background task.
            taskInstance.Canceled += OnTaskCanceled;
    
            // Retrieve the app service connection and set up a listener for incoming app service requests.
            var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            appServiceconnection = details.AppServiceConnection;
            appServiceconnection.RequestReceived += OnRequestReceived;
        }
    
        private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            // This function is called when the app service receives a request.
        }
    
        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (this.backgroundTaskDeferral != null)
            {
                // Complete the service deferral.
                this.backgroundTaskDeferral.Complete();
            }
        }
    }
    

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

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

    Срок жизни службы приложения зависит от вызывающей стороны.

    • Если вызывающий объект находится на переднем плане, время существования службы приложений совпадает с вызывающим.
    • Если вызывающий объект находится в фоновом режиме, служба приложений получает 30 секунд для запуска. Активация задержки дает службе пять дополнительных секунд.

    При отмене задачи вызывается onTaskCanceled . Задача отменяется, когда клиентское приложение удаляет AppServiceConnection, клиентское приложение приостановлено, ОПЕРАЦИОННая система завершает работу или спящий режим, или ОС выходит из ресурсов для выполнения задачи.

Написание кода для службы приложений

OnRequestReceived — код для службы приложений. Замените заглушку OnRequestReceived в Inventory.cs MyAppService кодом из этого примера. Этот код получает индекс складской позиции и передает его в строке команды в службу, чтобы получить название и цену указанной складской позиции. В собственные проекты добавьте код обработки ошибок.

private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
    // Get a deferral because we use an awaitable API below to respond to the message
    // and we don't want this call to get canceled while we are waiting.
    var messageDeferral = args.GetDeferral();

    ValueSet message = args.Request.Message;
    ValueSet returnData = new ValueSet();

    string command = message["Command"] as string;
    int? inventoryIndex = message["ID"] as int?;

    if (inventoryIndex.HasValue &&
        inventoryIndex.Value >= 0 &&
        inventoryIndex.Value < inventoryItems.GetLength(0))
    {
        switch (command)
        {
            case "Price":
            {
                returnData.Add("Result", inventoryPrices[inventoryIndex.Value]);
                returnData.Add("Status", "OK");
                break;
            }

            case "Item":
            {
                returnData.Add("Result", inventoryItems[inventoryIndex.Value]);
                returnData.Add("Status", "OK");
                break;
            }

            default:
            {
                returnData.Add("Status", "Fail: unknown command");
                break;
            }
        }
    }
    else
    {
        returnData.Add("Status", "Fail: Index out of range");
    }

    try
    {
        // Return the data to the caller.
        await args.Request.SendResponseAsync(returnData);
    }
    catch (Exception e)
    {
        // Your exception handling code here.
    }
    finally
    {
        // Complete the deferral so that the platform knows that we're done responding to the app service call.
        // Note for error handling: this must be called even if SendResponseAsync() throws an exception.
        messageDeferral.Complete();
    }
}

Обратите внимание, что OnRequestReceived является асинхронным, так как мы делаем ожидаемый вызов метода SendResponseAsync в этом примере.

Отсрочка принимается таким образом, чтобы служба может использовать асинхронные методы в обработчике OnRequestReceived . Это гарантирует, что вызов OnRequestReceived не завершится до тех пор, пока не будет обработано сообщение. SendResponseAsync отправляет результат вызывающей объекту. SendResponseAsync не сигнализирует о завершении вызова. Это завершение отсрочки, которая сигнализирует SendMessageAsync , что OnRequestReceived завершено. Вызов SendResponseAsync упаковывается в блок try/finally, так как необходимо завершить отсрочку, даже если SendResponseAsync создает исключение.

Службы приложений используют объекты ValueSet для обмена информацией. Размер данных, которые можно передать, ограничен только ресурсами системы. Не существует предопределенных параметров, которые можно использовать в ValueSet. Вам необходимо решить, какие значения параметров вы будете использовать для определения протокола для службы приложений. Вызывающий код необходимо писать с учетом этого протокола. В этом примере мы выбрали параметр с именем Command, значение которого указывает, что должна предоставить служба приложений: сведения об имени складской позиции или ее цене. Индекс имени складской позиции хранится в параметре ID. Возвращаемое значение хранится в параметре Result.

Перечисление AppServiceClosedStatus возвращается вызывающему объекту, чтобы указать, успешно ли выполнен вызов службы приложений или произошел сбой. Сбой вызова службы приложений может возникнуть, если ОС прерывает конечную точку службы, так как ее ресурсы исчерпаны. Вы можете вернуть дополнительные сведения об ошибке с помощью ValueSet. В этом примере мы используем параметр Status, чтобы возвращать в вызывающий код более подробные сведения об ошибке.

Вызов SendResponseAsync возвращает ValueSet вызывающей объекту.

Развертывание приложения службы и получение имени семейства пакетов

Перед вызовом поставщика службы приложений необходимо развернуть его от клиента. Его можно развернуть, выбрав "Создать > развертывание решения " в Visual Studio.

Для вызова пакета также потребуется имя семейства пакетов поставщика службы приложений. Его можно получить, открыв файл Package.appxmanifest проекта AppServiceProvider в представлении конструктора (дважды щелкните его в Обозреватель решений). Выберите вкладку "Упаковка", скопируйте значение рядом с именем семейства пакетов и вставьте его где-то, например Блокнот.

Написание клиента для вызова службы приложений

  1. Добавьте в решение новый пустой проект универсального приложения для Windows (для этого последовательно выберите пункты Файл > Добавить > Создать проект). В диалоговом окне "Добавление нового проекта" выберите "Установленное > пустое приложение Visual C# > (универсальное приложение Windows) и назовите его ClientApp.

  2. В проекте ClientApp добавьте следующую инструкцию using в начало MainPage.xaml.cs:

    using Windows.ApplicationModel.AppService;
    
  3. Добавьте текстовое поле с именем textBox и кнопку в MainPage.xaml.

  4. Добавьте обработчик нажатия кнопки для кнопки с именем button_Click и добавьте ключевое слово async в подпись обработчика кнопки.

  5. Замените заглушку обработчика нажатия кнопки указанным ниже кодом. Не забудьте включить объявление поля inventoryService.

    private AppServiceConnection inventoryService;
    
    private async void button_Click(object sender, RoutedEventArgs e)
    {
       // Add the connection.
       if (this.inventoryService == null)
       {
           this.inventoryService = new AppServiceConnection();
    
           // Here, we use the app service name defined in the app service 
           // provider's Package.appxmanifest file in the <Extension> section.
           this.inventoryService.AppServiceName = "com.microsoft.inventory";
    
           // Use Windows.ApplicationModel.Package.Current.Id.FamilyName 
           // within the app service provider to get this value.
           this.inventoryService.PackageFamilyName = "Replace with the package family name";
    
           var status = await this.inventoryService.OpenAsync();
    
           if (status != AppServiceConnectionStatus.Success)
           {
               textBox.Text= "Failed to connect";
               this.inventoryService = null;
               return;
           }
       }
    
       // Call the service.
       int idx = int.Parse(textBox.Text);
       var message = new ValueSet();
       message.Add("Command", "Item");
       message.Add("ID", idx);
       AppServiceResponse response = await this.inventoryService.SendMessageAsync(message);
       string result = "";
    
       if (response.Status == AppServiceResponseStatus.Success)
       {
           // Get the data  that the service sent to us.
           if (response.Message["Status"] as string == "OK")
           {
               result = response.Message["Result"] as string;
           }
       }
    
       message.Clear();
       message.Add("Command", "Price");
       message.Add("ID", idx);
       response = await this.inventoryService.SendMessageAsync(message);
    
       if (response.Status == AppServiceResponseStatus.Success)
       {
           // Get the data that the service sent to us.
           if (response.Message["Status"] as string == "OK")
           {
               result += " : Price = " + response.Message["Result"] as string;
           }
       }
    
       textBox.Text = result;
    }
    

    Замените имя семейства пакетов в строке this.inventoryService.PackageFamilyName = "Replace with the package family name"; именем семейства пакета проекта AppServiceProvider, которое вы получили выше в разделе Развертывание приложения службы и получение имени семейства пакетов.

    Примечание.

    Не забудьте вставить строковый литерал, а не поместить его в переменную. Он не будет работать, если вы используете переменную.

    Сначала код устанавливает связь со службой приложения. Подключение останется открытым, пока вы не удалите объект this.inventoryService. Имя службы приложений должно соответствовать AppService атрибуту элементаName, добавленного в файл Package.appxmanifest проекта AppServiceProvider. В нашем примере поисковый запрос будет выглядеть так: <uap3:AppService Name="com.microsoft.inventory"/>.

    Имя ValueSet message создается для указания команды, которую мы хотим отправить в службу приложений. Пример службы приложения будет ждать команду, чтобы указать, какое из 2 действий необходимо выполнить. Мы получаем индекс из текстового поля в клиентском приложении, а затем вызовем службу с Item помощью команды, чтобы получить описание элемента. Затем с помощью команды Price мы получаем цену элемента. Результат используется в качестве текста кнопки.

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

  6. Задайте для проекта ClientApp запуск проекта (щелкните его правой кнопкой мыши в Обозреватель решений> Set в качестве запуска проекта) и запустите решение. Введите число 1 в текстовое поле и нажмите кнопку. Вы должны получить "Председатель: Цена = 88,99" обратно из службы.

    Образец приложения, в котором отображаются данные: Chair price=88.99

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

  1. Убедитесь, что имя семейства пакетов, назначенное подключению службы инвентаризации, соответствует имени семейства пакетов приложения AppServiceProvider . См. строку в button_Click с this.inventoryService.PackageFamilyName = "...";.
  2. В button_Click убедитесь, что имя службы приложений, назначенное подключению службы инвентаризации, соответствует имени службы приложений в файле Package.appxmanifest AppServiceProvider. См. this.inventoryService.AppServiceName = "com.microsoft.inventory";.
  3. Убедитесь, что приложение AppServiceProvider развернуто. (В Обозреватель решений щелкните решение правой кнопкой мыши и выберите пункт "Развернуть решение".

Отладка службы приложений

  1. Перед отладкой убедитесь, что решение развернуто, так как перед вызовом службы необходимо развернуть приложение поставщика службы приложений. (Для этого в Visual Studio последовательно выберите пункты Сборка > Развернуть решение).
  2. В Обозреватель решений щелкните правой кнопкой мыши проект AppServiceProvider и выберите "Свойства". На вкладке Отладка измените значение параметра Действие при запуске на Не запускать, а отлаживать мой код при открытии. (Обратите внимание, что при использовании C++ для реализации поставщика службы приложений на вкладке Отладка необходимо изменить значение параметра Запуск приложения на Нет.)
  3. В проекте MyAppService в файле Inventory.cs установите точку останова в OnRequestReceived.
  4. Задайте для проекта AppServiceProvider запуск проекта и нажмите клавишу F5.
  5. Запустите ClientApp из меню (не из Visual Studio).
  6. Введите число 1 в текстовое поле и нажмите кнопку. Отладчик остановится на вызове службы приложений в точке ее останова.

Отладка клиента

  1. Для отладки клиента, вызывающего службу приложений, выполните инструкции из предыдущего шага.
  2. Запустите ClientApp из меню .
  3. Подключите отладчик к процессу ClientApp.exe (а не к процессу ApplicationFrameHost.exe ). (В Visual Studio последовательно выберите пункты Отладка > Присоединиться к процессу...).
  4. В проекте ClientApp задайте точку останова в button_Click.
  5. Точки останова в клиенте и службе приложений теперь будут поражены при вводе номера 1 в текстовое поле ClientApp и нажмите кнопку.

Устранение общих неполадок службы приложений

Если после попытки подключиться к службе приложений возникает состояние AppUnavailable , проверьте следующее:

  • Убедитесь, что проект поставщика службы приложений и проект службы приложений развернуты. Необходимо развернуть оба проекта перед запуском клиента, так как в противном случае клиенту не к чему будет подключаться. Развертывание можно выполнить в Visual Studio с помощью команды Сборка>Развернуть решение.
  • В Обозреватель решений убедитесь, что проект поставщика службы приложений содержит ссылку на проект в проект, реализующий службу приложений.
  • Убедитесь, что <Extensions> запись и его дочерние элементы были добавлены в файл Package.appxmanifest, принадлежащий проекту поставщика службы приложений, как указано выше, в Package.appxmanifest.
  • Убедитесь, что строка AppServiceConnection.AppServiceName в клиенте, вызывающая поставщика службы приложений, соответствует <uap3:AppService Name="..." /> указанному в файле Package.appxmanifest проекта поставщика приложений.
  • Убедитесь, что AppServiceConnection.PackageFamilyName соответствует имени семейства пакетов компонента поставщика службы приложений, как указано выше в разделе "Добавление расширения службы приложений в Package.appxmanifest"
  • Для служб приложений вне proc, таких как в этом примере, убедитесь, что EntryPoint указанный в <uap:Extension ...> элементе файла Package.appxmanifest проекта поставщика приложений соответствует пространству имен и имени класса общедоступного класса, реализующего IBackgroundTask в проекте службы приложений.

Устранение неполадок отладки

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

  • Убедитесь, что проект поставщика службы приложений и проект службы приложений развернуты. Оба проекта должны быть развернуты перед запуском клиента. Развертывание можно выполнить в Visual Studio с помощью команды Сборка>Развернуть решение.
  • Убедитесь, что проект, который требуется отладить, задан в качестве запускаемого проекта и что свойства отладки для этого проекта не запускаются при нажатии клавиши F5 . Щелкните правой кнопкой мыши проект, выберите пункт Свойства и щелкните Отладка (или Отладка в C++). При работе на C# измените значение параметра Действие при запуске на Не запускать, а отлаживать мой код при открытии. При работе на C++ задайте для параметра Запуск приложения значение Нет.

Замечания

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

  • Создайте фоновую задачу для размещения службы приложений.
  • windows.appService Добавьте расширение в файл Package.appxmanifest поставщика службы приложений.
  • Получите имя семейства пакетов поставщика службы приложений, чтобы подключиться к нему из клиентского приложения.
  • Добавьте ссылку на проект в проект поставщика службы приложений в проект службы приложений.
  • Используйте Windows.ApplicationModel.AppService.AppServiceConnection для вызова службы.

Полный код MyAppService

using System;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;

namespace MyAppService
{
    public sealed class Inventory : IBackgroundTask
    {
        private BackgroundTaskDeferral backgroundTaskDeferral;
        private AppServiceConnection appServiceconnection;
        private String[] inventoryItems = new string[] { "Robot vacuum", "Chair" };
        private double[] inventoryPrices = new double[] { 129.99, 88.99 };

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Get a deferral so that the service isn't terminated.
            this.backgroundTaskDeferral = taskInstance.GetDeferral();

            // Associate a cancellation handler with the background task.
            taskInstance.Canceled += OnTaskCanceled;

            // Retrieve the app service connection and set up a listener for incoming app service requests.
            var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            appServiceconnection = details.AppServiceConnection;
            appServiceconnection.RequestReceived += OnRequestReceived;
        }

        private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            // Get a deferral because we use an awaitable API below to respond to the message
            // and we don't want this call to get canceled while we are waiting.
            var messageDeferral = args.GetDeferral();

            ValueSet message = args.Request.Message;
            ValueSet returnData = new ValueSet();

            string command = message["Command"] as string;
            int? inventoryIndex = message["ID"] as int?;

            if (inventoryIndex.HasValue &&
                 inventoryIndex.Value >= 0 &&
                 inventoryIndex.Value < inventoryItems.GetLength(0))
            {
                switch (command)
                {
                    case "Price":
                        {
                            returnData.Add("Result", inventoryPrices[inventoryIndex.Value]);
                            returnData.Add("Status", "OK");
                            break;
                        }

                    case "Item":
                        {
                            returnData.Add("Result", inventoryItems[inventoryIndex.Value]);
                            returnData.Add("Status", "OK");
                            break;
                        }

                    default:
                        {
                            returnData.Add("Status", "Fail: unknown command");
                            break;
                        }
                }
            }
            else
            {
                returnData.Add("Status", "Fail: Index out of range");
            }

            // Return the data to the caller.
            await args.Request.SendResponseAsync(returnData);

            // Complete the deferral so that the platform knows that we're done responding to the app service call.
            // Note for error handling: this must be called even if SendResponseAsync() throws an exception.
            messageDeferral.Complete();
        }


        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (this.backgroundTaskDeferral != null)
            {
                // Complete the service deferral.
                this.backgroundTaskDeferral.Complete();
            }
        }
    }
}