Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UWP Background Task HttpClient fails when device screen is off

I'm working on a UWP app designed for phones. It's designed to sync data with a server running on your local home network. This syncing might take quite some time so a background task isn't the best place to sync the data; it'll probably take more than the 30 seconds I'm allotted. The idea, however, is to use a background task with a timer trigger; it'll call the server to check if there are any updates to consume and then pop up a toast notification asking if it can run in the foreground to perform the synchronization.

The code works great... if the screen is on. But if the screen is turned off, then I never get any notifications. At first I thought the timertrigger wasn't triggering, but I logged whenever it ran and sure enough, ir ran every 15 minutes on time. I looked deeper into it, and it's failing. Specifically, it's failing on the network call; HttpClient.GetAsync, with the following error:

"The text associated with this error code could not be found.\r\n\r\nA connection with the server could not be established\r\n"

Now I checked the server; it's running. I turn the screen on and the code suddenly works again. I've set up the trigger to only run when an unmetered connection is available:

    var status = await BackgroundExecutionManager.RequestAccessAsync();
    if(status.In(BackgroundAccessStatus.DeniedBySystemPolicy, BackgroundAccessStatus.DeniedByUser))
    {
        return;
    }

    var builder = new BackgroundTaskBuilder();
    builder.Name = Constants.BackgroundTaskName;
    builder.SetTrigger(new TimeTrigger(15, false));
    builder.AddCondition(new SystemCondition(SystemConditionType.FreeNetworkAvailable));
    BackgroundTaskRegistration task = builder.Register();

So I would think that the timer only gets triggered when the Wifi is available. But then when I actually perform the HTTP Get using this code:

    async protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
    {
        if (BackgroundWorkCost.CurrentBackgroundWorkCost == BackgroundWorkCostValue.High)
            return;
        if (!NetworkInterface.GetIsNetworkAvailable())
            return;
        base.OnBackgroundActivated(args);
        if (args.TaskInstance.Task.Name == Constants.BackgroundTaskName)
        {
            var cancel = new CancellationTokenSource();
            args.TaskInstance.Canceled += (s, e) =>
            {
                cancel.Cancel();
                cancel.Dispose();
            };
            var deferral = args.TaskInstance.GetDeferral();
            try
            {
                HttpClient client = GetClient();
                var response = await client.GetAsync(ConstructUrl(client.BaseAddress, "updates"), cancel.Token);
                var info = await ParseHttpResponse<UpdateInformation>(response);     
            }
            catch { }
            finally
            {
                deferral.Complete();
            }
        }

Now the funny thing is, NetworkInterface.GetIsNetworkAvailable() returns "true", telling me there's a network available. But still, when I make the call, I get "A connection with the server could not be established". I have no idea what I'm doing wrong here.

Any ideas?

like image 625
Ron Penton Avatar asked Aug 24 '16 21:08

Ron Penton


2 Answers

It is very likely that you are required to specify "IsNetworkRequested" on your background task registration in order for the network to be functional during connected standby (which occurs while the screen is off).

Refer to the documentation here: https://docs.microsoft.com/en-us/uwp/api/Windows.ApplicationModel.Background.BackgroundTaskBuilder

like image 178
Javier N Flores Assad Avatar answered Oct 08 '22 18:10

Javier N Flores Assad


Though this is an old Question, this is still valid as of today.. I got the answer from this post HttpClient GetAsync fails in background task on Windows 8 ..

Adding the answer here for future developers who run into this issue.

The HttpClient will not work when call to the method, in which it is instantiated is not await - ed as the background task will not wait for the HttpClient to finish its task and continue execution to the end of the flow.

For example:

The following won't work when the initiate() method is called from the Run(IBackgroundTaskInstance taskInstance) of your background task class.

//In Your background task class
public void Run(IBackgroundTaskInstance taskInstance)
{
  BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
  initiate();
  deferral.Complete();
}
public async void initiate()
{
   //some code 
   HttpClient client = new HttpClient();
   HttpResponseMessage responseMessage = await client.GetAsync(new Uri(url));      
}

Solution:

//In Your background task class
public async void Run(IBackgroundTaskInstance taskInstance)
{
   BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
   await initiate(); 
   //Background task will wait for the initiate() to complete then call defferal.Complete().
   deferral.Complete();
}
public async Task initiate()
{
  //some code 
  HttpClient client = new HttpClient();
  HttpResponseMessage responseMessage = await client.GetAsync(new Uri(url));      
}
like image 25
Pratyay Avatar answered Oct 08 '22 19:10

Pratyay