Приложения на Universal Windows Platform могут использовать фоновые задачи (background tasks). Фоновые задачи позволяют выполнять работу приложения даже в тот момент, когда оно неактивно или не находится на переднем фоне.
И также важно понимать, что фоновые задачи в UWP - это не стандартные службы Windows, которые можно запустить и которые потом смогут непрерывно постоянно выполнять некоторые действия. При создании фоновых задач на UWP мы сталкиваемся с различными ограничениями. Прежде всего фоновым задачам гарантировано выделяется не более 10% ресурсов CPU. Фоновые задачи, за исключением долговременных, должны выполняться по времени в течение 25 секунд и еще дополнительно 5 секунд выделяется на завершение задачи.
В плане оперативной памяти фоновые задачи ограничены объемом примерно в 16 Мб (хотя точное количество доступной оперативной памяти может варьироваться в зависимости от устройства)
Итак, для работы с фоновыми задачами создадим новый проект UWP. Пусть он будет называться BackgroundTaskApp.
Для создания фоновых задач добавим в решение новый проект по типу Windows Runtime Component:
Пусть новый проект будет называться MyRuntimeComponent. В итоге в одном решении будут два проекта: основной UWP и проект компонента фоновной задачи.
Из проекта компонента фоновой задачи удалим пустой класс Class1, который добавляется по умолчанию, и добавим новый класс MyBackgroundTask (или переименуем Class1 в MyBackgroundTask). В итоге получится следующая структура проектов:
И сразу добавим в главный проект ссылку на проект фоновой задачи.
Компонент фоновой задачи должен реализовать интерфейс IBackgroundTask. Поэтому для начала изменим класс MyBackgroundTask следующим образом:
using Windows.ApplicationModel.Background; public sealed class MyBackgroundTask : IBackgroundTask { public void Run(IBackgroundTaskInstance taskInstance) { BackgroundTaskDeferral _deferral = taskInstance.GetDeferral(); // некоторая работа _deferral.Complete(); } }
Класс фоновой задачи определяет один метод Run(), который срабатывает при запуске фоновой задачи. Собственно он и предназначен для выполнения всех действий, который призвана выполнять данная задача.
Когда необходимо запустить фоновую задачу, система создает объект класса фоновой задачи и объект интерфейса IBackgroundTaskInstance, который служит для передачи параметров в задачу.
Объект IBackgroundTaskInstance определяет метод GetDeferral()
, который информирует систему о завершении задачи с помощью метода Complete()
.
IBackgroundTaskInstance также определяет ряд свойств:
InstanceId: возвращает id задачи
Progress: возвращает или устанавливает статус выполнения фоновой задачи
Task: получает доступ к фоновой задаче
Теперь определим более менее реальную задачу, которая бы выполняла какие-нибудь действия. Пусть, наша задача выполняет подсчет факториала некоторого числа. Итак, изменим код задачи следующим образом:
using System.Threading; using System.Threading.Tasks; using Windows.ApplicationModel.Background; using Windows.Storage; public sealed class MyBackgroundTask : IBackgroundTask { volatile bool _cancelRequested = false; // прервана ли задача public async void Run(IBackgroundTaskInstance taskInstance) { // оценка стоимости выполнения задачи для приложения var cost = BackgroundWorkCost.CurrentBackgroundWorkCost; if (cost == BackgroundWorkCostValue.High) return; // обрабатываем прерывание задачи var cancel = new CancellationTokenSource(); taskInstance.Canceled += (s, e) => { cancel.Cancel(); cancel.Dispose(); _cancelRequested = true; }; BackgroundTaskDeferral _deferral = taskInstance.GetDeferral(); await DoWork(taskInstance); _deferral.Complete(); } private async Task DoWork(IBackgroundTaskInstance taskInstance) { // получаем локальные настройки приложения var settings = ApplicationData.Current.LocalSettings; int number = (int)settings.Values["number"]; uint result = 1; for (uint progress = 1; progress <= number; progress++) { if (_cancelRequested) // если задача прервана, выходим из цикла { break; } result *= progress; await Task.Delay(1500); // имитация долгого выполнения // рассчет процентов выполнения taskInstance.Progress = (uint)(progress * 100 / number); // 1 * 100 / 6 } settings.Values["factorial"] = result; } }
Прежде всего при выполнении задачи необходимо оценить ее стоимость для текущего приложения. Может оказаться, что ресурсов для выполнения данной задачи не хватает, поэтому приложение может работать некорректно. Поэтому в случае высокой стоимости задачи для приложения прекращаем ее выполнение:
var cost = BackgroundWorkCost.CurrentBackgroundWorkCost; if (cost == BackgroundWorkCostValue.High) return;
Далее устанавливаем действия на случай прерывания задачи:
var cancel = new CancellationTokenSource(); taskInstance.Canceled += (s, e) => { cancel.Cancel(); cancel.Dispose(); _cancelRequested = true; };
Затем идут непосредственно действия по вычислению факториала, которые сосредоточены в методе DoWork. Вначале получаем из локальных настроек число. Если задача прервана,
выходим из цикла. Для задержки выполнения, чтобы в пользовательском интерфейсе мы смогли бы увидеть процесс выполнения, используется метод Task.Delay(1500)
.
Однако опять же надо помнить, что мы ограничены 25 секундами, поэтому надо учитывать при временных задержках.
Также надо отметить установку прогресса задачи:
taskInstance.Progress = (uint)(progress * 100 / number);
Изменения этого свойства потом позволят в пользовательском интерфейсе отображать ход выполнения задачи.