Обобщенный процесс разработки и использования в своем коде фоновой задачи
Создаем библиотеку, в которую помещаем код исполнения фоновой задачи для этого:
- Добавляем в Solution еще один проект ClassLibrary;
- В свойствах проекта устанавливаем “Output type” в Windows Runtime Component, чтобы у нас при сборке получался .winmd файл, а не dll’ка;
- Создаем класс который реализует IBackgroundTask.
Регистрируем в системе фоновую задачу:
- В манифесте основного (именно основного, не нужно в манифест библиотеки прописывать использование фоновых задач) приложения отмечаем, что будем использовать фоновые задачи;
- В коде основного приложения используя BackgroundTaskBuilder создаем экземпляр фоновой задачи;
- Устанавливаем триггеры при срабатывании которых задача будет запускаться;
- Устанавливаем условия запуска фоновой задачи (если нужно, они могут быть пустыми);
- Регистрируем в системе фоновую задачу;
- Подписываемся на события.
Практически все, что нужно для реализации фоновых задач собрано в пространстве имен Windows.ApplicationModel.Background.
Пространство имен содержит:
- 13 классов:
- BackgroundExecutionManager
- BackgroundTaskBuilder
- BackgroundTaskCompletedEventArgs
- BackgroundTaskDeferral
- BackgroundTaskProgressEventArgs
- BackgroundTaskRegistration
- MaintenanceTrigger
- NetworkOperatorHotspotAuthenticationTrigger
- NetworkOperatorNotificationTrigger
- PushNotificationTrigger
- SystemCondition
- SystemTrigger
- TimeTrigger
- 3 делагата
- 4 перечисления
- 5 интерфейсов
Реализация интерфейса 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 метода:
- SetTrigger(IBackgroundTrigger trigger) – устанавливает триггер по срабатыванию которого начинает работу (проверив условия заданные при помощи AddCondition) фоновая задача;
- AddCondition(IBackgroundCondition condition) – задаёт условие при которых должна выполнятся фоновая задача;
- 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, указывает один ли раз срабатывать.
Привожу полный перечень событий:
Событие | Значение | Описание |
---|---|---|
Invalid | 0 | Недопустимое значение |
SmsReceived | 1 | Фоновая задача срабатывает при получении SMS |
UserPresent | 2 | Срабатывает, когда пользователь входит в систему (снимает экран блокировки). Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером |
UserAway | 3 | Срабатывает, когда пользователь покидает систему (появляется экран блокировки). Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером |
NetworkStateChange | 4 | Срабатывает, когда изменяются параметры сети, например отключение/подключение или изменяется стоимость трафика при переходе в роуминг. |
ControlChannelReset | 5 | Срабатывает при перезагрузке канала связи. Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером |
InternetAvailable | 6 | Фоновая задача срабатывает, когда интернет становится доступен. |
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 | Фоновая задача может запускаться только если пользователь присутствует (рабочий стол не находится под экраном блокировки). |
UserNotPresent | 2 | Фоновая задача может запускаться только если пользователь отсутствует. |
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.
Собственно в перечислении и содержится причина по которой запрошена отмена выполнения фоновой задачи.
Привожу полный список :
Член | Значение | Причина |
---|---|---|
Abort | 0 | Фоновая задача была отменена приложением. Это может произойти по четырем причинам:
|
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 группы:
- Запрос доступа к экрану блокировки
- RequestAccessAsync()
- RequestAccessAsync(String)
- RequestAccessAsync()
- Проверка статуса доступа к экрану блокировки
- GetAccessStatus()
- GetAccessStatus(String)
- GetAccessStatus()
- Отмена доступа к экрану блокировки
- RemoveAccess()
- RemoveAccess(String)
- RemoveAccess()
Если функция вызывается без параметра, то делается это для приложения из которого она вызывается, но можно и запросить или удалить доступ для другого приложения, если указать его идентификатор в качестве параметра.
Функции запроса и проверки статуса возвращают перечисления (члены перечисления? ) BackgroundAccessStatus.
Привожу все возможные состояния:
Название | Значение | Описание |
---|---|---|
Unspecified | 0 | Пользователь не стал нажимать кнопки "разрешить" или "не разрешать" в диалоговом окне, просто закрыв его. Фактически доступа в этом случае нет. Нужно снова запрашивать доступ при помощи RequestAccessAsync(). |
AllowedWithAlwaysOnRealTimeConnectivity | 1 | Пользователь выбрал "разрешить" в диалоговом окне. Приложение добавлено на экран блокировки и может , ему доступно использование RTC брокера в режиме пониженного энергопотребления (connected standby). Чтобы получить такой статус нужно чтобы в манифесте приложения указывалось, что необходим доступ к RTC. Из восьми приложений располагающихся на экране блокировки доступ к RTC в режиме пониженного энергопотребления могут получить только три, поэтому, даже если вы правильно все указали в манифесте, система может и не выдать вам такое разрешение. При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно. |
AllowedMayUseActiveRealTimeConnectivity | 2 | Пользователь выбрал "разрешить" в диалоговом окне. Приложение может запускать фоновые задачи, в режиме пониженного энергопотребления приложение может не работать. При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно. |
Denied | 3 | Пользователь выбрал "не разрешать" в диалоговом окне. Приложение не добавлено на экран блокировки. При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно. |
Пример кода:
BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();Осталось 0 класса, 0 делегатов, 0 перечислений и 0 интерфейсов.
switch (status)
{
case BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity:
//Есть доступ к RTC, даже в режиме пониженного энергопотребления
lockScreenAdded = true;
break;
case BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity:
//Есть доступ к RTC, но не в режиме пониженного энергопотребления
break;
case BackgroundAccessStatus.Denied:
//Ой беда, беда… Огорчение…
break;
}
Комментариев нет:
Отправить комментарий