Rafsan
Rafsan

Reputation: 11

How to work with background tasks in UWP apps

I'm developing a UWP app which uses in-process background task to download new GitHub notifications and show them as toast notifications. I'm using MVVM light. I tried In-process background tasks with TimeTrigger, everything works as expected when the app is in foreground or minimized. But when the app is closed, the background tasks stop working. After monitoring task manager, I found that the background task launches but is closed immediately just after launching. So i used ExtendedExecutionSession to extend the suspension. Still it isn't working. Using Out of the process background tasks resolves the previous problem but introduces a new problem. The GitHub api call failed in the background due to inaccessibility to the app data (GitHub Auth Data) which is required to make the API call.

In short, I have the following problems:

  1. In process background tasks do not run in background when the app is closed
  2. Out of the process background tasks can share app data (for instance GitHub Auth data in my case) when I make an API call to GitHub using Octokit

I've found these workarounds:

  1. Using ExtendedExecutionSession with the in-process background tasks
  2. Using Inter process communication with the out of the process background tasks

My questions are:

  1. How and when should I call ExtendedExecutionSession in In-process tasks.

  2. How to code Inter-process communication in order to be able to access the app data from the background app

Note that I've registered the background tasks in MainPage.xaml.cs. Here is the code snippet:

var syncBuilder = Helpers.BackgroundTaskHelper.BuildBackgroundTask(
                        "SyncNotifications",
                        new TimeTrigger(15, false),
                        internetAvailableCondition,
                        userPresentCondition,
                        sessionConnectedCondition
                   );
syncBuilder.IsNetworkRequested = true;            
syncBuilder.Register();

And I overridden the OnBackgroundActivated in the App.xaml.cs. Here is the code spippet:

var deferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Task.Completed += BackgroundTask_Completed;
var taskName = args.TaskInstance.Task.Name;
switch (taskName)
{
     case "ToastNotificationBackgroundTask":
          await CoreApplication
          .MainView
          .CoreWindow
          .Dispatcher
          .RunAsync(
               CoreDispatcherPriority.Normal, 
               async () =>
               {
                   var toastTriggerDetails = args.TaskInstance.TriggerDetails as ToastNotificationActionTriggerDetail;
                   var toastArgs = QueryString.Parse(toastTriggerDetails.Argument);
                        var notificationId = toastArgs["notificationId"];
                   if (!notificationId.IsNulloremptyOrNullorwhitespace())
                   {
                        await NotificationsService.MarkNotificationAsRead(notificationId);
                   }
               });
          break;
     case "SyncNotifications":
          await CoreApplication
          .MainView
          .CoreWindow
          .Dispatcher
          .RunAsync(
               CoreDispatcherPriority.Normal, 
               async () =>
               {                            
                    AppViewmodel.UnreadNotifications = await NotificationsService.GetAllNotificationsForCurrentUser(false, false);
                    await AppViewmodel.UnreadNotifications?.ShowToasts();
               });
          break;
 }

Upvotes: 1

Views: 1810

Answers (1)

Stefan Wick  MSFT
Stefan Wick MSFT

Reputation: 13850

The problem in your code is that you are completing the Deferral too early, i.e. before your asynchronous dispatcher work items had a chance to execute. This happens to work when the app is in the foreground because the process will stay around anyway, but when activated in the background the process will only run until you mark the deferral as complete, meaning it will exit before your task code had a chance to run.

To see what I mean, try a little experiment. Run this code that mimics your current implementation under the debugger and watch in which order the breakpoints are being hit. This will help you realize that you are calling Deferral.Complete before your actual code gets to run:

protected async override void OnBgActivated()
{
    await CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
    {
        await PerformTaskAsync();
        Debugger.Break();
    });
    Debugger.Break();
}

private async Task PerformTaskAsync()
{
    await Task.Yield();
    Debugger.Break();
    return;
}

Now the solution for you is simple, move the Deferral.Complete() call to a place in your code after your task is actually complete. You may have to change the scope of 'deferral' for this and make a class-level variable.

Upvotes: 1

Related Questions