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

Важно!

Списки кода в этом разделе относятся только к 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 и указать, что приложение является поставщиком службы приложений. Сама служба будет реализована в виде фоновой задачи. Проект приложения службы приложений предоставляет службу другим приложениям. Для имени службы мы рекомендуем использовать обратное доменное имя.

Обратите внимание, что xmlns:uap4 префикс пространства имен и uap4:SupportsMultipleInstances атрибут действительны только в том случае, если вы используете 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 и замените код заглушки для Class1 новым классом фоновой задачи с именем 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.csMyAppService кодом из этого примера. Этот код получает индекс складской позиции и передает его в строке команды в службу, чтобы получить название и цену указанной складской позиции. В собственные проекты добавьте код обработки ошибок.

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 и добавьте асинхронный ключевое слово в сигнатуру обработчика кнопки.

  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 в качестве запускаемого проекта (щелкните его правой кнопкой мыши в Обозреватель решений>Настроить как запускаемый проект) и запустите решение. Введите число 1 в текстовое поле и нажмите кнопку. Служба возвратит следующие данные: "Chair : Price = 88.99".

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

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

  1. Убедитесь, что имя семейства пакетов, назначенное подключению к службе инвентаризации, совпадает с именем семейства пакетов приложения AppServiceProvider . См. строку в button_Click с this.inventoryService.PackageFamilyName = "...";.
  2. В button_Click убедитесь, что имя службы приложений, назначенное подключению к службе инвентаризации, совпадает с именем службы приложений в файле Package.appxmanifestAppServiceProvider. См. 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 с помощью команды Build>Deploy Solution .
  • В Обозреватель решений убедитесь, что проект поставщика службы приложений содержит ссылку между проектами на проект, реализующий службу приложений.
  • Убедитесь, что <Extensions> запись и ее дочерние элементы добавлены в файл Package.appxmanifest , принадлежащий проекту поставщика службы приложений, как указано выше в разделе Добавление расширения службы приложений в Package.appxmanifest.
  • Убедитесь, что строка AppServiceConnection.AppServiceName в клиенте, вызывающая поставщика службы приложений, совпадает с <uap3:AppService Name="..." /> строкой, указанной в файле Package.appxmanifest проекта поставщика службы приложений.
  • Убедитесь, что AppServiceConnection.PackageFamilyName соответствует имени семейства пакетов компонента поставщика службы приложений, как указано выше в разделе Добавление расширения службы приложений в Package.appxmanifest.
  • Для сторонних служб приложений, таких как в этом примере, убедитесь, что 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();
            }
        }
    }
}