понедельник, 29 октября 2012 г.

Памятка: Разработка фоновых задач. Реализация классов из пространства имен Windows.ApplicationModel.Background.

Обобщенный процесс разработки  и использования в своем коде фоновой задачи

Создаем библиотеку, в которую помещаем код исполнения фоновой задачи для этого:

  • Добавляем в Solution еще один проект ClassLibrary;
  • В свойствах проекта устанавливаем “Output type” в Windows Runtime Component, чтобы у нас при сборке получался .winmd файл, а не dll’ка;
  • Создаем класс который реализует IBackgroundTask.

Регистрируем в системе фоновую задачу:

  • В манифесте основного (именно основного, не нужно в манифест библиотеки прописывать использование фоновых задач) приложения отмечаем, что будем использовать фоновые задачи;
  • В коде основного приложения используя BackgroundTaskBuilder создаем экземпляр фоновой задачи;
  • Устанавливаем триггеры при срабатывании которых задача будет запускаться;
  • Устанавливаем условия запуска фоновой задачи (если нужно, они могут быть пустыми);
  • Регистрируем в системе фоновую задачу;
  • Подписываемся на события.

Практически все, что нужно для реализации фоновых задач собрано в пространстве имен Windows.ApplicationModel.Background.

Пространство имен содержит:

Реализация интерфейса IBackgroundTask.

Интерфейс содержит всего один метод Run(IBackgroundTaskInstance taskInstance). Именно этот метод выполняется при запуске фоновой задачи.

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

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

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance)
{
//Здесь код выполнения
}
}

Осталось 13 классов, 3 делегата, 4 перечисления и 3 интерфейса

Run может быть помечен, как async в этом случае следует использовать класс BackgroundTaskDeferral который оповещает систему о том, что работа еще не закончилась.

Выглядит это примерно так:

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
try
{
await FuncNameAsync();
}
finally { deferral.Complete(); }
}
}

Если вы используете модель асинхронных вызовов предлагаемую WinRT, то код будет выглядеть примерно так:

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
IAsyncAction op1 = Something.DoAsync();
op1.Completed = new AsyncActionCompletedHandler(
(IAsyncAction act, AsyncStatus stat) =>
{
deferral.Complete();
});
}
}

BackgroundTaskDeferral содержит всего один метод Complete();


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


Осталось 12 классов, 3 делегата, 4 перечисления и 3 интерфейса


Регистрация фоновой задачи в системе:


Лучше всего пожалуй будет разбирать на примере кода, вот наиболее раздутый пример:

private void RegisterBackgroundTasks() 
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
//Название фоновой задачи, может не совпадать с именем класса
builder.Name = "BackgroundTestClass";
// Имя класса
builder.TaskEntryPoint = "BackgroundTaskLibrary.TestClass";
IBackgroundTrigger trigger = new TimeTrigger(15, true);
builder.SetTrigger(trigger);
IBackgroundCondition condition = new SystemCondition(SystemConditionType.InternetAvailable);
builder.AddCondition(condition);
IBackgroundTaskRegistration task = builder.Register();
task.Progress += new BackgroundTaskProgressEventHandler(task_Progress);
task.Completed += new BackgroundTaskCompletedEventHandler(task_Completed);
}

Класс BackgroundTaskBuilder позволяет регистрировать фоновую задачу в системе.

У класса есть конструктор, 2 свойства (Name и TaskEntryPoint) и 3 метода:


  1. SetTrigger(IBackgroundTrigger trigger) – устанавливает триггер по срабатыванию которого начинает работу (проверив условия заданные при помощи AddCondition) фоновая задача;
  2. AddCondition(IBackgroundCondition condition) – задаёт условие при которых должна выполнятся фоновая задача;
  3. Register() – Регистрирует фоновую задачу в системе. Возвращает BackgroundTaskRegistration (который реализует IBackgroundTaskRegistration ).

Используя BackgroundTaskRegistration можно отменять регистрацию фоновой задачи при помощи вызова метода Unregister или перебирать все зарегистрированные приложением фоновые задачи используя свойство AllTasks.

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

Пример кода:

private void RegisterBackgroundTasks() 
{
string taskName = "BackgroundTestClass";
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == taskName)
{
return;
}
}
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = taskName;
//и тд…
}

Осталось 11 классов, 3 делегата, 4 перечисления и 2 интерфейса

 


Триггеры исполнения фоновых задач


Всего Windows.ApplicationModel.Background содержит 6 классов реализующих IBackgroundTrigger :






















MaintenanceTrigger Представляет собой триггер оповещающий о возможности произвести обслуживание системы.
NetworkOperatorHotspotAuthenticationTrigger Представляет триггер срабатывающий при аутентификации в беспроводной сети.
NetworkOperatorNotificationTrigger Представляет триггер срабатывающий на получении уведомлений от оператора связи.
PushNotificationTrigger Представляем собой триггер срабатывающий при получении приложением сырого уведомления (raw notification).
SystemTrigger Триггер реагирующий на системные события.
TimeTrigger Триггер срабатывающий через определенные интервалы времени.

 TimeTrigger

Класс срабатывающий по истечении временного интервала. TimeTrigger можно использовать только в приложениях размещенных на экране блокировки.


Конструктор

public TimeTrigger(
uint freshnessTime,
bool oneShot
)

принимает два параметра: первый указывает интервал исполнения в минутах, второй – флаг указывающий, что задача должна выполнится только 1 раз. Если параметр oneShot установить в false, то задача будет выполняться раз в freshnessTime минут.


Пример регистрации:

private bool RegisterTimeTriggerBackgroundTask()       
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = "Background task with TimeTrigger";
builder.TaskEntryPoint = "LibName.TimeTriggerBackgroundTask";
// Запускаем раз в 15 минут
IBackgroundTrigger trigger = new TimeTrigger(15, false);
builder.SetTrigger(trigger);
IBackgroundTaskRegistration task = builder.Register();
return true;
}

MaintenanceTrigger


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


Параметры конструктора идентичны и не требуют пояснений.

public MaintenanceTrigger(
uint freshnessTime,
bool oneShot
)

Пример регистрации:

private bool RegisterMaintenanceBackgroundTask()
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = "Maintenance background task";
builder.TaskEntryPoint = "LibName.MaintenaceBackgroundTask";
// Запускается каждые 8 часов, если устройство подключено к сети
IBackgroundTrigger trigger = new MaintenanceTrigger(480, false);
builder.SetTrigger(trigger);
IBackgroundTaskRegistration task = builder.Register();
return true;
}

Примечание: Триггеры фоновых задач, привязанные к временным интервалам (MaintenanceTrigger, TimeTime) не могут отрабатывать чаще, чем раз в 15 минут. При попытке зарегистрировать задачу с использованием интервала меньше 15 минут вылетит Exeption.


SystemTrigger


Представляет собой триггер реагирующий на системные события.


Конструктор

public SystemTrigger(
SystemTriggerType triggerType,
bool oneShot
)

Принимает два параметра: первый содержит системное событие из перечисления SystemTriggerType, второй, также как  и у TimeTrigger’a, указывает один ли раз срабатывать.


Привожу полный перечень событий:




























































СобытиеЗначениеОписание
Invalid0

Недопустимое значение

SmsReceived1

Фоновая задача срабатывает при получении SMS

UserPresent2

Срабатывает, когда пользователь входит в систему (снимает экран блокировки).


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

UserAway3

Срабатывает, когда пользователь покидает систему (появляется экран блокировки).


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

NetworkStateChange4

Срабатывает, когда изменяются параметры сети, например отключение/подключение или изменяется стоимость трафика при переходе в роуминг.

ControlChannelReset 5

Срабатывает при перезагрузке канала связи.


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

InternetAvailable6

Фоновая задача срабатывает, когда интернет становится доступен.

SessionConnected 7

Срабатывает, когда пользователь логинится.


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

ServicingComplete 8

Фоновая задача срабатывает после окончания обновления приложения.

LockScreenApplicationAdded 9

Срабатывает, при добавлении приложения на экран блокировки.

LockScreenApplicationRemoved 10

Срабатывает, когда приложение удаляется с  экрана блокировки.

TimeZoneChange 11

Срабатывает при изменении временной зоны на устройстве (например, при переходе с зимнего на летнее время).

OnlineIdConnectedStateChange 12

Срабатывает когда изменяется статус подключения аккаунта Microsoft (бывший LiveId).


Пример регистрации:

private bool RegisterSystemTriggerBackgroundTask()
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = "SystemTrigger background task";
builder.TaskEntryPoint = "LibName.SystemBackgroundTask";
IBackgroundTrigger trigger = new SystemTrigger(InternetAvailable, false);
builder.SetTrigger(trigger);
IBackgroundTaskRegistration task = builder.Register();
return true;
}

PushNotificationTrigger


Представляет собой триггер, срабатывающий при получении Row Notification.

Имеет 2 конструктора:

public PushNotificationTrigger()
public PushNotificationTrigger(string applicationId)

Второй конструктор в качестве параметра получает идентификатор приложение (Package Relative Application ID (PRAID)) и выполняет фоновые действия для него. Следует отметить, что приложение из которого регистрируется фоновая задача и приложение идентификатор которого передается в конструктор должны находится в одном пакадже.

Последние 2 триггера

NetworkOperatorHotspotAuthenticationTrigger

NetworkOperatorNotificationTrigger

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

Дополнительную информацию можно посмотреть здесь.

Осталось 5 классов, 3 делегата, 3 перечисления и 1 интерфейс


Условия исполнения.


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


Условий на выполнение может быть указано  0 или более. Другими словами условия указывать необязательно.


Создается условие при помощи вызова конструктора SystemCondition (реализует интерфейс IBackgroundCondition) который в качестве параметра получает элемент перечисления SystemConditionType


После этого условие добавляется используя метод AddCondition у TaskBuilder’а. Чтобы задать несколько условий, необходимо создать несколько экземпляров SystemCondition и для каждого вызвать AddCondition.


Привожу полный список элементов перечисления:




































ЭлементЗначениеОписание
Invalid 0

Недопустимое значение условия

UserPresent 1

Фоновая задача может запускаться только если пользователь присутствует (рабочий стол не находится под экраном блокировки).

UserNotPresent2

Фоновая задача может запускаться только если пользователь отсутствует.

InternetAvailable 3

Фоновая задача может запускаться только если устройство подключено к интернету.

InternetNotAvailable 4

Фоновая задача может запускаться только если устройство не подключено к интернету.

SessionConnected 5

Фоновая задача может запускаться если пользователь открыл рабочую сессию (ввел логин и пароль).

SessionDisconnected 6

Фоновая задача может запускаться, только если нет залогиневшегося пользователя.


Пример использования:

var sampleTaskBuilder = new BackgroundTaskBuilder();
//[skip]
//Создаем условия исполненияSystemCondition userCondition = new SystemCondition(UserPresent);
SystemCondition internetCondition = new SystemCondition(InternetAvailable);
//Устанавливаем условия
sampleTaskBuilder.AddCondition(userCondition);
sampleTaskBuilder.AddCondition(internetCondition);

Осталось 4 класса, 3 делегата,2 перечисления и 1 интерфейс


Обработка прогресса выполнения фоновой задачи


Для того, чтобы отслеживать прогресс исполнения фоновой задачи необходимо подписаться на событие Progress класса BackgroundTaskRegistration (реализует интерфейс IBackgroundTaskRegistration) используя делегат BackgroundTaskProgressEventHandler


В качестве параметров обработчик события получает 2 параметра: экземпляр IBackgrowndTaskRegistration и BackgroundTaskProgressEventArgs.


У BackgroundTaskProgressEventArgs есть свойство Progress, которое и содержит процент выполнения задачи.


Пример кода:

private void task_Progress(IBackgroundTaskRegistration task, BackgroundTaskProgressEventArgs args)          
{
var progress = "Progress: " + args.Progress + "%";
BackgroundTaskSample.ServicingCompleteTaskProgress = progress;
UpdateUI();
}

Осталось 3 класса, 2 делегата,2 перечисления и 0 интерфейсов


Обработка окончания выполнения фоновой задачи


Для того, чтобы обработать результаты выполнения фоновой задачи необходимо подписаться на событие Completed класса BackgroundTaskRegistration  используя делегат BackgroundTaskCompletedEventHandler. В качестве параметров обработчик получает все тот же экземпляр IBackgrowndTaskRegistration и BackgroundTaskCompletedEventHandler.


Типовым решением является сохранение результатов исполнения в LocalStttings используя в качестве ключа идентификатор фоновой задачи (BackgroundTaskRegistrationю.TaskId).


Чтобы проверить, что фоновая задача не вызвала исключение, следует вызывать метод BackgroundTaskCompletedEventHandler.CheckResult(), который пробрасывает исключение с которым завершилось выполнение фоновой задачи в основное приложение.


Пример кода:

private void OnCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
{
var settings = ApplicationData.Current.LocalSettings;
var key = task.TaskId.ToString();
try
{
args.CheckResult();
//Получаем результат исполнения
var result = settings.Values[key].ToString();
//Используем результат при необходимости
}
catch (Exception ex)
{
//сообщаем об ошибке
}
}

Осталось 2 класса, 1 делегат,2 перечисления и 0 интерфейсов


Отмена выполнения фоновой задачи


Отмена выполнения фоновой задачи может происходить по нескольким причинам. Обработку отмены нужно производить в классе реализующем IBackgroundTask, для этого нужно подписаться на событие Canceled экземпляра IBackgroundTaskInstans передаваемого в метод Run.


Обработчик события имеет тип BackgroundTaskCanceledEventHandler и получает два параметра IBackgroundTaskInstance и элемент перечисления   BackgroundTaskCancellationReason.


Собственно в перечислении и содержится причина по которой запрошена отмена выполнения фоновой задачи.


Привожу полный список :
























ЧленЗначениеПричина
Abort0

Фоновая задача была отменена приложением. Это может произойти по четырем причинам:



  • Фоновая задача определяется системой как бездействующая.
  • Фоновая задача активировалась при помощи MaintenanceTrigger но во время выполнения система перешла на работу от батареек.
  • Приложение отменило регистрацию фоновой задачи, и потому выполнение отменяется.
  • Приложение которое зарегистрировало фоновую задачу деинсталировано во время выполнения.
Terminating 1

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

LoggingOff 2

Выполнение фоновой задачи произошло, потому-что пользователь вышел из системы.

ServicingUpdate 3

Отмена фоновой задачи произошла из-за того, что приложение обновляется.


Типовым решение для обработки отмены является использования флага в классе реализующем IBackgroundTask с периодическим опрашиванием в методе Run значения этого флага.


Пример кода:

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
volatile bool isCancelRequested = false;
void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance)
{
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(task_Canceled);//Здесь код выполнения
Operation1();
if (!isCancelRequested)
Operation2();
//и тд…
}

private void task_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
isCancelRequested = true;
}
}

Отмена из основной программы делается следующим образом:

string name = "ExampleTaskName";
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == name)
{
cur.Value.Unregister(true);
}
}

Осталось 1 класса, 0 делегат,1 перечисление и 0 интерфейсов


Итак для изучения у нас остался последний класс BackgroundExecutionManager, который предоставляет методы для доступа к экрану блокировки. Класс предоставляет 6 методов, которые можно разбить на 3 группы:



  • Запрос доступа к экрану блокировки

    1. RequestAccessAsync()
    2. RequestAccessAsync(String)
    В этом случае пользователю показывается диалоговое окно с вопросом о разрешении добавить приложение на экран блокировки.
  • Проверка статуса доступа к экрану блокировки

    1. GetAccessStatus()
    2. GetAccessStatus(String)
    Окно не показывается, но проверка производится. Функция нужна, т.к. после разрешения или отказа от размещения на экране блокировки, состояние доступа может быть изменено в настройках системы.
  • Отмена доступа к экрану блокировки

    1. RemoveAccess()
    2. RemoveAccess(String)

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


Функции запроса и проверки статуса возвращают перечисления (члены перечисления? ) BackgroundAccessStatus.


Привожу все возможные состояния:
























НазваниеЗначениеОписание
Unspecified0

Пользователь не стал нажимать кнопки "разрешить" или  "не разрешать"  в диалоговом окне, просто закрыв его.


Фактически доступа в этом случае нет. Нужно снова запрашивать доступ при помощи  RequestAccessAsync().

AllowedWithAlwaysOnRealTimeConnectivity 1

Пользователь выбрал "разрешить" в диалоговом окне. Приложение добавлено на экран блокировки и может , ему доступно использование RTC брокера  в режиме пониженного энергопотребления  (connected standby).


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


При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно.

AllowedMayUseActiveRealTimeConnectivity 2

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


При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно.

Denied 3

Пользователь выбрал "не разрешать" в диалоговом окне. Приложение не добавлено на экран блокировки.


При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно.


Пример кода:

BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
switch (status)
{
case BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity:
//Есть доступ к RTC, даже в режиме пониженного энергопотребления
lockScreenAdded = true;
break;
case BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity:
//Есть доступ к RTC, но не в режиме пониженного энергопотребления
break;
case BackgroundAccessStatus.Denied:
//Ой беда, беда… Огорчение…
break;
}
Осталось 0 класса, 0 делегатов, 0 перечислений и 0 интерфейсов.

      Комментариев нет: