WinRT gotcha - "one shot" Timer BackgroundTask does not unregister after the "one shot"

Development WinUI

8 years ago

First up - sorry Jack Reacher fans, this post is not about the novel :-) . In my latest Windows 10 app Event Countdowns I made a very unlucky mistake. When registering its Background Task that should update the app's tiles every 30 minutes, I used the following code:

string myTaskName = "TileUpdaterBackgroundTask";

// check if task is already registered
var task =
    BackgroundTaskRegistration.AllTasks.Where( cur => cur.Value.Name == myTaskName ).Select( c => c.Value ).SingleOrDefault();
if ( task != null )
{
    //do not register again                    
    return;
}

var backgroundAccess = await BackgroundExecutionManager.RequestAccessAsync();

if ( backgroundAccess == BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity ||
    backgroundAccess == BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity )
{
    // register a new task
    BackgroundTaskBuilder taskBuilder = new BackgroundTaskBuilder
    {
        Name = myTaskName,
        TaskEntryPoint = "TileUpdateTask.TileUpdateBackgroundTask"
    };

    taskBuilder.SetTrigger( new TimeTrigger( 30, true ) );

    BackgroundTaskRegistration myFirstTask = taskBuilder.Register();
}

At first glance, nothing out of the ordinary. Or so I thought. One day after the app was released, I checked my pinned tile counting down to the premiere of Captain America: Civil War to find out with my immense surprise, that the time left on the tile was off by a day! I immediately checked the code and after a few minutes of intense debugging frenzy found the hidden evil - the second parameter of the TimeTrigger class' constructor.

taskBuilder.SetTrigger( new TimeTrigger( 30, true ) );

The second parameter, oneShot, specifies, whether the task will be triggered only once (value true) or periodically (value false). My course of action was to just change true to false and because the app worked well when tested on my PC, I published the updated version to Store. When the update arrived on my phone, I happily installed it, launched it and waited for the tiles to become "live" again. So I waited. And I waited. And I waited. And... nothing. I was lost. The app worked perfectly on my PC and but tiles just didn't update on my phone. So I went back to the drawing (Visual) board (Studio) and thought it all through again. And then I saw it.

// check if task is already registered
var task =
    BackgroundTaskRegistration.AllTasks.Where( cur => cur.Value.Name == myTaskName ).Select( c => c.Value ).SingleOrDefault();
if ( task != null )
{
    //do not register again
    return;
}

I naturally presumed, that once the oneShot Background Task is triggered, it is automatically unregistered by the system. In fact, that is not the case.

Once a oneShot trigger runs the Background Task, the task still stays registered!

This means that when I installed the updated app on my phone and run it, the if check for task != null evaluated to true, because the task was still registered, although it has already fired once! The final solution was then to force unregister the existing task to make sure the task is registered again with oneShot set to false. The bug is finally captured and tiles live on!