One of the ‘interesting’ things about a Windows Store application and the application lifecycle is that it forces architectural changes onto the way that you’d build an app.
You can read the full details on the developer centre if you’ve not looked at this before but the “cut down version” is;
- your app code runs when your app is on the screen in front of the user
- your app code gets suspended when your app is not
- your suspended app can be killed by the OS if it needs the resources
This would be seriously limiting if it’s all that the system offered the app developer but there are additions in that there are background services that your app can make use of to cater for (2) and (3). Services like;
- Background audio.
- Background uploads/downloads.
- Custom background work implemented by tasks.
Tasks are sparked up in response to triggers firing on the system. Triggers can be things like the user logging in or the network connectivity changing or something along those lines and a trigger can have conditions applied such that an additional check is placed on them before they run. For instance – the trigger “user present” might be given a condition of “internet available” so that it only fires in those circumstances.
Some categories of trigger are available to be used by all apps and at all times, other categories are only available to an app that the user has put on their lock screen and a subset of triggers will only run if the user has their device plugged in rather than when they are running on the battery.
On top of this, the work that a trigger can do is restricted in terms of CPU (time) usage and also in terms of the network bandwidth that it’s allowed to consume and, again, the network part of this changes depending on whether the device is powered or on battery.
From a .NET perspective, a background task is a WinRT component that implements one interface with one method called Run.
The application needs to register the tasks that it has along with the triggers that they are associated with and would usually do this the first time that it runs up. The application needs to take the right steps to ask the user to put the app onto the lock-screen if that’s something that the app’s triggers need to work and there’s guidance around that on the developer centre.
There’s not, as far as I know, any way to register the app’s background tasks when the app is installed so if the app never got ran, I don’t think its background tasks could run.
I was working with this the other day and had written a background task which I eventually simplified down to this;
public sealed class MyTask : IBackgroundTask { public void Run(IBackgroundTaskInstance taskInstance) { // for doing async stuff... var deferral = taskInstance.GetDeferral(); PopToast("Hello"); deferral.Complete(); } }
The PopToast function just displays a toast notification so I have made sure that my app manifest specifies that I can do toast notifications and that function (for completeness) looks like;
void PopToast(string toast) { // using Windows.UI.Notifications. ToastNotifier toastNotifier = ToastNotificationManager.CreateToastNotifier(); // using Windows.Data.Xml.Dom XmlDocument xmlToastContent = ToastNotificationManager.GetTemplateContent( ToastTemplateType.ToastText01); TemplateUtility.CompleteTemplate( xmlToastContent, new string[] { "Hello" }, null, "ms-winsoundevent:Notification.Mail"); ToastNotification toastNotification = new ToastNotification(xmlToastContent); toastNotifier.Show(toastNotification); }
and, again, for completion the TemplateUtility class is just a really thrown-together thing that I use to make my life easier when it comes to filling in the XML templates that Windows 8 push notifications make use of;
internal static class TemplateUtility { public static void CompleteTemplate(XmlDocument xml, string[] text, string[] images = null, string sound = null) { XmlNodeList slots = xml.SelectNodes("descendant-or-self::image"); int index = 0; if ((images != null) && (slots != null)) { while ((index < images.Length) && (index < slots.Length)) { ((XmlElement)slots[index]).SetAttribute("src", images[index]); index++; } } if (text != null) { slots = xml.SelectNodes("descendant-or-self::text"); index = 0; while ((slots != null) && ((index < text.Length) && (index < slots.Length))) { slots[index].AppendChild(xml.CreateTextNode(text[index])); index++; } } if (!string.IsNullOrEmpty(sound)) { var audioElement = xml.CreateElement("audio"); audioElement.SetAttribute("src", sound); xml.DocumentElement.AppendChild(audioElement); } } }
When it comes to registering the background task, I run this code;
if ((BackgroundTaskRegistration.AllTasks == null) || (BackgroundTaskRegistration.AllTasks.Count == 0)) { BackgroundTaskBuilder builder = new BackgroundTaskBuilder(); builder.Name = "MyTask"; builder.TaskEntryPoint = typeof(MyBackTask.MyTask).FullName; builder.SetTrigger(new SystemTrigger(SystemTriggerType.NetworkStateChange, false)); builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable)); builder.Register(); }
which is attempting to register the trigger when the NetworkStateChanges and limit that to only when the InternetAvailable flag is true. On line 6 of the code above, I use the MyBackTask WinRT component that I’ve added a reference to purely in order to be able to get hold of its type’s full name as part of the registration step.
I made sure that my app’s manifest is set up to specify that I’m doing background tasks;
and then I can run my code to do the registration.
What I found when using this particular trigger with my home office network is that the background task ran a lot. It tended to run almost as soon as I ran the registration code and it tended to run whether my network changed or not.
I wanted it to run at most once when the network state went from “unavailable” to “available” and that really wasn’t happening for me.
I think this is more than likely a “feature” of my machine or my networking setup but it definitely happens and it means that the trigger fires in situations where I wasn’t expecting it to.
What to do?
The first thing I did was to take advice and use a ‘better’ trigger for my scenario and take away the condition that I was using. That is, I changed my registration code to;
if ((BackgroundTaskRegistration.AllTasks == null) || (BackgroundTaskRegistration.AllTasks.Count == 0)) { BackgroundTaskBuilder builder = new BackgroundTaskBuilder(); builder.Name = "MyTask"; builder.TaskEntryPoint = typeof(MyBackTask.MyTask).FullName; builder.SetTrigger(new SystemTrigger(SystemTriggerType.InternetAvailable, false)); builder.Register(); }
and (because of the way I’d written my code) I then uninstalled the app and reinstalled it to get rid of my background registrations.
Using the InternetAvailable trigger seemed to give me a lot fewer ‘false positive’ results. That is – it didn’t seem to fire when my network state hadn’t changed from unavailable to available.
However, it still did seem to fire more frequently than I’d hoped that it might – I could get multiple instances of the trigger running when my network state went unavailable->available.
With that in mind, it seems like a trigger like this that wants to run once needs to;
- protect itself a little from the possibility of running multiple times in quick succession
- no doubt, also protect itself a little from the scenario where it gets an “InternetAvailable” trigger but perhaps the internet isn’t quite available when the task runs but maybe is available a few seconds later.
There’s also another aspect of this.
If the foreground app is running when the background task executes then there’s a couple of ways that the two parts of the app can communicate with each other;
- The IBackgroundTask interface Run method is passed an IBackgroundTaskInstance interface which has a Progress() method which the background task can use to report a uint value across the boundary to the foreground app.
- That progress value is picked up by the foreground app code getting hold of a BackgroundTaskRegistration object for one or more of its background tasks and then handling the Progress or Completed events provided by that registration object. A BackgroundTaskRegistration is returned when you register a task or when you get hold of existing registrations via BackgroundTaskRegistration.AllTasks.
The ‘problem’ that causes for me is that the BackgroundTaskRegistration.Completed event will fire every time my background task runs, even if that’s not a ‘real’ execution of the task. The other ‘problem’ is that reporting an integer isn’t very verbose so it’s not easy to get real data back to the foreground task.
It’d be nice to have something that solved those problems by only allowing the task to run so many times in a particular time period and facilitated the reporting of richer data between the background task and the foreground app whenever that background task completed.
As far as I know, the background task doesn’t necessarily (or by default) run inside of the foreground app’s process so communication between the two needs to be via some out-of-band mechanism like maybe the file system or what I’ve experimented with is using the ApplicationData.LocalSettings property bag ( as documented here http://msdn.microsoft.com/en-us/library/windows/apps/windows.storage.applicationdata.localsettings.aspx ).
Now, I’m sure this is a ‘hijacking’ of this particular piece of functionality but given that one of those settings can be up to 64K in size, I figure it could be used to report a serialized object from the background task to the foreground app and also to mark the point in time where the background task has last run successfully to avoid it running more frequently than I want it to.
The following code’s pretty sketchy and largely untested but it’s what I made use of so far.
If I have some data-type that I want to use to pass data from the background task to the foreground task on completion of that background task then I might define it;
[DataContract] public class MyResult { [DataMember] public int Val1 { get; set; } [DataMember] public long Val2 { get; set; } [DataMember] public string Val3 { get; set; } [DataMember] public double Val4 { get; set; } [DataMember] public bool Val5 { get; set; } }
and I might put that into a library that both the foreground app and the background task project can reference so that they can share a type. I can do the same with a couple of helper classes;
public class EventArgsWithPayload<T> : EventArgs { public EventArgsWithPayload(T payload) { this.Payload = payload; } public T Payload { get; private set; } } internal static class BackgroundTaskAppSettings { internal static bool RanInPeriod(TimeSpan period) { bool ran = false; object oValue = null; if (Container.Values.TryGetValue(LAST_TIME_MARKER, out oValue)) { long lastRunTicks = (long)oValue; DateTime dateTime = new DateTime(lastRunTicks); DateTime now = DateTime.Now; ran = (now - dateTime) < period; } return (ran); } internal static void MarkSuccessRunTime() { Container.Values[LAST_TIME_MARKER] = DateTime.Now.Ticks; } internal static bool IsRunning { get { return (Container.Values.ContainsKey(RUNNING_MARKER)); } } internal static void SetRunning() { Container.Values[RUNNING_MARKER] = true; } internal static void ClearRunning() { Container.Values.Remove(RUNNING_MARKER); } internal static void StoreLastTaskResult<T>(T lastResult) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); MemoryStream stream = new MemoryStream(); serializer.WriteObject(stream, lastResult); byte[] bits = stream.ToArray(); string strBits = Convert.ToBase64String(bits); if (strBits.Length > (32 * 1024)) { throw new InvalidOperationException("data too large"); } Container.Values[LAST_VALUE_MARKER] = strBits; } internal static T GetLastTaskResult<T>() { string strBits = (string)Container.Values[LAST_VALUE_MARKER]; byte[] bits = Convert.FromBase64String(strBits); MemoryStream stream = new MemoryStream(bits); DataContractJsonSerializer serializer = new DataContractJsonSerializer( typeof(T)); T result = (T)serializer.ReadObject(stream); return (result); } static ApplicationDataContainer Container { get { if (_container == null) { _container = ApplicationData.Current.LocalSettings.CreateContainer(CONTAINER, ApplicationDataCreateDisposition.Always); } return (_container); } } internal static readonly uint MAGIC_PROGRESS_VALUE = 0xDEADDEED; static ApplicationDataContainer _container; const string CONTAINER = "backTaskContainer"; const string RUNNING_MARKER = "running"; const string LAST_TIME_MARKER = "lastRunTime"; const string LAST_VALUE_MARKER = "lastValue"; } public class BackgroundTaskNotifier<T> { public EventHandler<EventArgsWithPayload<T>> Completed; public BackgroundTaskNotifier(string taskName) { // Going to throw if we can't find your task. IBackgroundTaskRegistration reg = BackgroundTaskRegistration.AllTasks .Where(t => t.Value.Name == taskName) .Select(t => t.Value) .Single(); reg.Progress += (s, e) => { if (e.Progress == BackgroundTaskAppSettings.MAGIC_PROGRESS_VALUE) { // Something we're interested in. if (this.Completed != null) { var payload = BackgroundTaskAppSettings.GetLastTaskResult<T>(); var args = new EventArgsWithPayload<T>(payload); this.Completed(this, args); } } }; } } public class BackgroundTaskRunner<T> { IBackgroundTaskInstance _instance; TimeSpan _minimumPeriodBetweenRuns; Func<Task<T>> _action; public BackgroundTaskRunner(IBackgroundTaskInstance instance, TimeSpan minimumPeriodBetweenRuns, Func<Task<T>> action) { this._instance = instance; this._minimumPeriodBetweenRuns = minimumPeriodBetweenRuns; this._action = action; } public async Task RunAsync() { var deferral = this._instance.GetDeferral(); // This is really a race. I'm treating the setting of a local // setting as though it's somehow atomic. It's not very likely // that it is and my get/set on it certainly isn't. if (!BackgroundTaskAppSettings.RanInPeriod(this._minimumPeriodBetweenRuns) && !BackgroundTaskAppSettings.IsRunning) { BackgroundTaskAppSettings.SetRunning(); try { // Don't try and handle exceptions for you here. T result = await this._action(); // Magic progress value that can be picked up on the foreground // app side to mean "completed" because the real completed // event fires whether we really run the task or not. BackgroundTaskAppSettings.StoreLastTaskResult(result); this._instance.Progress = BackgroundTaskAppSettings.MAGIC_PROGRESS_VALUE; BackgroundTaskAppSettings.MarkSuccessRunTime(); } finally { BackgroundTaskAppSettings.ClearRunning(); } } deferral.Complete(); } }
and then I can make use of these. In my foreground app, once I know that I’ve registered a task called “MyTask” I can hook up an event handler to its (hijacked) Completed event;
BackgroundTaskNotifier<MyResult> notifier = new BackgroundTaskNotifier<MyResult>("MyTask"); notifier.Completed += (s, e) => { var data = e.Payload; };
and in the implementation of the background task itself, I can try and make sure that the task doesn’t run too often;
public sealed class MyTask : IBackgroundTask { public async void Run(IBackgroundTaskInstance taskInstance) { BackgroundTaskRunner<MyResult> runner = new BackgroundTaskRunner<MyResult>( taskInstance, TimeSpan.FromMinutes(1), async () => { PopToast("Hello"); MyResult r = new MyResult() { Val1 = 1, Val2 = 2, Val3 = "x", Val4 = 1.0d, Val5 = false }; return (r); } ); await runner.RunAsync(); } }
and that seems to (mostly) do the job for me in at least as far as this work in progress goes for today