1
votes

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;
 }
1
Have you checked if your code is crashing when the foreground app is not running? One thing to keep in mind is that your app does not have a view when it is not running, so if your background task code makes assumptions about having a view it may fail/crash. Specificially in your code I see it depends on CoreApplication.MainView - which may be null during background activation.Stefan Wick MSFT
The other thing to check is your deferral. I don't see when/how you are completing it. If it just goes out of scope at the end of the method then it will get marked as complete and the task will immediately exit, before your dispatcher actions had a chance to run to completion. You need to hold on to the deferral until you are actually done with your task. Come to think about it, this is likely the issue you are hitting.Stefan Wick MSFT
@StefanWickMSFT that was a mistake editing this post. I actually used deferral.Complete(); at the end of the code.Rafsan
I also tried without using CoreApplication.MainViewand got the same result.Rafsan
Please clarify what you mean by "end of the code". Do you mean end of the method, or after you have actually completed your task? Also have you checked whether or not your code is hitting an exception/crash here?Stefan Wick MSFT

1 Answers

1
votes

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.