Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Adding a Background Task to the Windows Platform Applications

At some point you’re likely to want to run code in the background – this might be to update live tiles, or to do periodic synchronization of data. In this post I’ll add a background task to the Windows platform applications. I’ll start by adding a new Windows Runtime Component to the solution.

image

As a Windows RT component there are some additional restrictions on the definition of types and the way methods are exposed. However, I can still add a reference to the background project to the Core library of the application. As the Core library encapsulates all the logic for the application this should be sufficient to perform background operations such as synchronizing data or updating tile contents.

Next I want to add a reference from the Universal applications (Windows and Windows Phone) to the background task project. Once done, it’s time to create the actual task that will be invoked in the background. I’ll replace the default Class1.cs with SyncTask.cs with the following templated structure:

public sealed class SyncTask : IBackgroundTask
{
    private BackgroundTaskCancellationReason cancelReason = BackgroundTaskCancellationReason.Abort;
    private volatile bool cancelRequested = false;
    private BackgroundTaskDeferral deferral = null;
    //
    // The Run method is the entry point of a background task.
    //
    public async void Run(IBackgroundTaskInstance taskInstance)
    {
        try
        {
            Debug.WriteLine("Background " + taskInstance.Task.Name + " Starting...");

            //
            // Get the deferral object from the task instance, and take a reference to the taskInstance;
            //
            deferral = taskInstance.GetDeferral();
            //
            // Associate a cancellation handler with the background task.
            //
            taskInstance.Canceled += OnCanceled;

            //
            // Query BackgroundWorkCost
            // Guidance: If BackgroundWorkCost is high, then perform only the minimum amount
            // of work in the background task and return immediately.
            var cost = BackgroundWorkCost.CurrentBackgroundWorkCost;

            if (cost == BackgroundWorkCostValue.High)
            {
                // Only push changes
            }
            else
            {
                // Do full sync
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
        finally
        {
            if (deferral != null)
            {
                deferral.Complete();
            }
        }
    }

    //
    // Handles background task cancellation.
    //
    private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
        //
        // Indicate that the background task is canceled.
        //
        cancelRequested = true;
        cancelReason = reason;
        Debug.WriteLine("Background " + sender.Task.Name + " Cancel Requested...");
    }
}

I also need to define this task as a background task in the Declarations section of the project manifest files. In this case we’re going to be triggering the background task based on a system event.

image

Lastly, the background task needs to be registered. For this I’m using this BackgroundTaskManager class which wraps the registration process for tasks.

public class BackgroundTaskManager
{
    /// <summary>
    /// Register a background task with the specified taskEntryPoint, name, trigger,
    /// and condition (optional).
    /// </summary>
    /// <param name="taskEntryPoint">Task entry point for the background task.</param>
    /// <param name="name">A name for the background task.</param>
    /// <param name="trigger">The trigger for the background task.</param>
    /// <param name="condition">An optional conditional event that must be true for the task to fire.</param>
    public static async Task<BackgroundTaskRegistration> RegisterBackgroundTask(String taskEntryPoint, String name, IBackgroundTrigger trigger, IBackgroundCondition condition)
    {
        BackgroundExecutionManager.RemoveAccess();
        var hasAccess = await BackgroundExecutionManager.RequestAccessAsync();
        if (hasAccess == BackgroundAccessStatus.Denied) return null;

        var builder = new BackgroundTaskBuilder();
        builder.Name = name;
        builder.TaskEntryPoint = taskEntryPoint;
        builder.SetTrigger(trigger);
        BackgroundTaskRegistration task = builder.Register();
        Debug.WriteLine(task);

        return task;
    }

    /// <summary>
    /// Unregister background tasks with specified name.
    /// </summary>
    /// <param name="name">Name of the background task to unregister.</param>
    /// <param name="cancelRunningTask">Flag that cancels or let task finish job</param>
    public static void UnregisterBackgroundTasks(string name, bool cancelRunningTask)
    {
        //
        // Loop through all background tasks and unregister any with SampleBackgroundTaskName or
        // SampleBackgroundTaskWithConditionName.
        //
        foreach (var cur in BackgroundTaskRegistration.AllTasks)
        {
            if (cur.Value.Name == name)
            {
                cur.Value.Unregister(cancelRunningTask);
            }
        }
    }
}

The actual registration is done when the application starts. In this case I’m going to cheat a little and include it in the OnNavigatedTo for the MainPage:

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    try
    {
        BackgroundTaskManager.UnregisterBackgroundTasks("Background Sync Task", true);
        BackgroundTaskRegistration taskReg =
            await BackgroundTaskManager.RegisterBackgroundTask(typeof(SyncTask).FullName,
                "Background Sync Task",
                new SystemTrigger(SystemTriggerType.InternetAvailable, false),
                null);
    }
    catch (Exception ex)
    {
        // Swallow this to ensure app doesn't crash in the case of back ground tasks not registering
        Debug.WriteLine(ex.Message);
    }
}

Notice that this task is registering interest in the InternetAvailable trigger, allowing the background task to be invoked whenever connectivity changes. Note that this process works for both Windows and Windows Phone Universal projects.

Update:

What I forgot to include here is that if you want tasks to run in the background on the Windows platform you need to make them lock screen enabled. To do this set the “Lock screen notifications” to either Badge or Badge and Tile Text.

image

This will cause a couple of red circles indicating issues with the configuration of the application. The first is that you need to specify either location, timer, control channel or push notifications in the Declarations tab – I’ve chosen to check the push notification checkbox

image

The other requirement is a badge logo – I’ve only specified one of the three options. I’d recommend providing all three if you are serious about delivering a high quality application.

image

Update 2:

When it actually came to run this I must confess I ran into an issue with a typo. Back on the second screenshot an observant reader would have noticed the spelling mistake on the namespace RealEstateInspector. When you attempt to register the service I saw something similar to the following:

image

Full exception details:

System.Exception occurred
  HResult=-2147221164
  Message=Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))

Pingbacks and trackbacks (1)+

Comments are closed