Is there an event to handle when connection status change?
For example, my app is like OutLook. I would like to handle an event to know when there is Internet connection and then to send all pending e-mails.
Now I can check periodically if there is Internet connection, but it seems a poor solution to me. I would to solve it using an event.
Your best choice would be implementing a background task. This way you could send the pending e-mails even if your app is not open any more when the internet connection becomes available.
When registering a background task you can set a trigger and a condition to configure when you want your task to run:
var trigger = new SystemTrigger(SystemTriggerType.InternetAvailable, false);
var condition = new SystemCondition(SystemConditionType.InternetAvailable);
var builder = new BackgroundTaskBuilder();
builder.Name = "Send pending e-mails task";
builder.TaskEntryPoint = "Tasks.SendPendingEmailTask";
builder.SetTrigger(trigger);
builder.AddCondition(condition);
var task = builder.Register();
Your background task must implement the IBackgroundTask interface. When the task is triggered, the Run method will be called:
public sealed class SendPendingEmailTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
var deferral = taskInstance.GetDeferral();
// send your e-mails here
deferral.Complete();
}
}
Related
I am using a UWP project with a background task that is triggered by the internet being available. Once triggered, a toast notification is displayed.
The problem is that the background task seems to only run once after launching the UWP application. it even works after closing the application and restarting my computer as long as I haven't triggered it before doing so, but only if it is untriggered before restarting.
What am I doing wrong? am I missing something or misusing the background task?
For clarification, I want it to send a notification every time the internet is connected. The background task should run independent of the main application.
Below is the code for the background task:
namespace AppService
{
public sealed class testNoteUpdaterTask : IBackgroundTask
{
BackgroundTaskDeferral _deferral; // Note: defined at class scope so that we can mark it complete inside the OnCancel() callback if we choose to support cancellation
public void Run(IBackgroundTaskInstance taskInstance)
{
// Get a deferral so that the service isn't terminated.
_deferral = taskInstance.GetDeferral();
// Construct the content
new ToastContentBuilder()
.AddArgument("action", "testNote")
.AddArgument("conversationId", 9813)
.AddText("Program update avaliable for testNote")
// Buttons
.AddButton(new ToastButton()
.SetContent("testNote stuff")
.AddArgument("action", "open")
.SetBackgroundActivation())
.Show();
_deferral.Complete();
}
}
}
And here is the code which I use to register the background task inside the main UWP application:
public static BackgroundTaskRegistration RegisterBackgroundTask(string taskEntryPoint, string taskName, IBackgroundTrigger trigger, IBackgroundCondition condition = null)
{
// Check for existing registrations of this background task.
foreach (var cur in BackgroundTaskRegistration.AllTasks) {
if (cur.Value.Name == taskName){
// The task is already registered.
return (BackgroundTaskRegistration)(cur.Value);
}
}
// Register the background task.
var builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = taskEntryPoint;
builder.SetTrigger(trigger);
if (condition != null) {
builder.AddCondition(condition);
}
BackgroundTaskRegistration task = builder.Register();
return task;
}
public MainPage()
{
this.InitializeComponent();
RegisterBackgroundTask("AppService.testNoteUpdaterTask", "testNoteUpdaterX", new SystemTrigger(SystemTriggerType.InternetAvailable, true));
}
I checked your code. It seems that when you are registering the SystemTrigger here:
new SystemTrigger(SystemTriggerType.InternetAvailable, true)
You are setting the oneShot parameter as true, which means the system event trigger will be used only once. Please set this value to false if you want the system event trigger to be used every time the event occurs.
More information here:SystemTrigger(SystemTriggerType, Boolean) Constructor.
Please use the following code:
new SystemTrigger(SystemTriggerType.InternetAvailable, false)
You could also take a look at the official background task sample here:
BackgroundTask Sample line 166.
I have an app (App1) that makes use of the WKWebView for a good portion of the UI. There is a scenario where an HTTP PUT request is sent from the WKWebView to a backend server to save some data. For this save operation to complete, the server will need approval thru another app (App2). The user would normally switch to App2 to approve, then switch back to App1 to see the result of the save. The problem is that when App1 gets backgrounded, it can cause the response to the save request to be cancelled, even though the save was completely successful on the backend server. There isn't any errors actually logged, but I'm fairly certain it is happening because iOS is killing the connection when the app gets suspended after it gets backgrounded. I'm basing my thoughts on this discussion.
Since the time it takes to approve the save on App2 isn't that long, I figured I could just try to extend the background time of App1, and it appears to work in the times I've tested it.
However, I want to know if this is really the best strategy, and if so, are there any recommendations on my code (For example, should I move the BeginBackgroundTask inside of the Task.Run):
I used these microsoft docs as an example.
public override async void DidEnterBackground(UIApplication application)
{
ExtendBackgroundTime(application);
}
private nint? webViewBgTaskId = null;
private CancellationTokenSource webViewBgTaskTokenSrc = null;
private void ExtendBackgroundTime(UIApplication application)
{
// cancel the previous background task that was created in this function
webViewBgTaskTokenSrc?.Cancel();
webViewBgTaskTokenSrc = null;
if (webViewBgTaskId.HasValue)
{
application.EndBackgroundTask(webViewBgTaskId.Value);
webViewBgTaskId = null;
}
var cts = new CancellationTokenSource();
nint taskId = default;
taskId = application.BeginBackgroundTask(() =>
{
cts.Cancel();
webViewBgTaskTokenSrc = null;
application.EndBackgroundTask(taskId);
webViewBgTaskId = null;
});
_ = Task.Run(async () =>
{
// For now, this is just set to 5 minutes, but in my experience,
// the background task will never be allowed to continue for that long.
// It's usually only about 30 seconds as of iOS 13.
// But this at least gives it some finite upper bound.
await Task.Delay(TimeSpan.FromMinutes(5), cts.Token);
application.EndBackgroundTask(taskId);
webViewBgTaskId = null;
}, cts.Token);
webViewBgTaskTokenSrc = cts;
webViewBgTaskId = taskId;
}
The following code snippet demonstrates registering a task to run in the background:
nint taskID = UIApplication.SharedApplication.BeginBackgroundTask( () => {});
//runs on main or background thread
FinishLongRunningTask(taskID);
UIApplication.SharedApplication.EndBackgroundTask(taskID);
The registration process pairs a task with a unique identifier, taskID, and then wraps it in matching BeginBackgroundTask and EndBackgroundTask calls. To generate the identifier, we make a call to the BeginBackgroundTask method on the UIApplication object, and then start the long-running task, usually on a new thread. When the task is complete, we call EndBackgroundTask and pass in the same identifier. This is important because iOS will terminate the application if a BeginBackgroundTask call does not have a matching EndBackgroundTask.
Note: If you want to perform Tasks During DidEnterBackground method, these tasks must be invoked on a separate thread. Therefore, sample project uses Task to invoke FinishLongRunningTask.
Task.Factory.StartNew(() => FinishLongRunningTask(taskID));
I am trying to add a BT server to my app, and using the "BluetoothRfcommChat" sample project, ("Scenario3_BgChatServer" to be exact - with background task).
It seems that if I register the background task and terminate the app without unregistering - I cant register again (even after reboot).
How can I kill a UWP background task?
Thanks in advance.
Background task registration persists across reboot. You want that to be the case.
If your app wants to renew the registration, you will need to unregister first. You would do this along the lines of:
foreach (var bgTask in BackgroundTaskRegistration.AllTasks)
{
if (bgTask.Value.Name == "MaintenanceTask")
{
bgTask.Value.Unregister(true);
}
}
var requestTask = BackgroundExecutionManager.RequestAccessAsync();
var builder = new BackgroundTaskBuilder();
builder.Name = "MaintenanceTask";
builder.TaskEntryPoint = "BackgroundTasks.MaintenanceTask";
builder.SetTrigger(new MaintenanceTrigger(360, false));
BackgroundTaskRegistration task = builder.Register();
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?
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://learn.microsoft.com/en-us/uwp/api/Windows.ApplicationModel.Background.BackgroundTaskBuilder
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));
}
I am writing Windows Phone 8.1 Application using GeoFence API. My problem is that I can't trigger change of location in Background Task, because app exits with code 1.
I have read multiple threads about this error, but no solution solved my problem.
I have checked if my BackgroundTask is a Runtime Component, and it is.
I have checked name of my class and it is correct.
I have checked if I use any await function in my BackgroundTask function and I didn't find any.
I have checked if I registered Background Task in app manifest and yes, I did (with entry point ofc)
In fact error appears even before running Run function from BackgroundTask.
namespace BackgroundTask
{
public sealed class geoFenceBackgroundTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
ToastTemplateType toastTemplate = ToastTemplateType.ToastText02;
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
toastTextElements[0].AppendChild(toastXml.CreateTextNode("MY APP"));
toastTextElements[1].AppendChild(toastXml.CreateTextNode("Test"));
//IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
//XmlElement audio = toastXml.CreateElement("audio");
ToastNotification toast = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
}
}
And my register function:
async private void RegisterBackgroundTask()
{
// Get permission for a background task from the user. If the user has already answered once,
// this does nothing and the user must manually update their preference via PC Settings.
BackgroundAccessStatus backgroundAccessStatus = await BackgroundExecutionManager.RequestAccessAsync();
// Regardless of the answer, register the background task. If the user later adds this application
// to the lock screen, the background task will be ready to run.
// Create a new background task builder
BackgroundTaskBuilder geofenceTaskBuilder = new BackgroundTaskBuilder();
geofenceTaskBuilder.Name = "geoFenceBackgroundTask";
geofenceTaskBuilder.TaskEntryPoint = "BackgroundTask.geoFenceBackgroundTask";
// Create a new location trigger
var trigger = new LocationTrigger(LocationTriggerType.Geofence);
// Associate the locationi trigger with the background task builder
geofenceTaskBuilder.SetTrigger(trigger);
// If it is important that there is user presence and/or
// internet connection when OnCompleted is called
// the following could be called before calling Register()
// SystemCondition condition = new SystemCondition(SystemConditionType.UserPresent | SystemConditionType.InternetAvailable);
// geofenceTaskBuilder.AddCondition(condition);
// Register the background task
var geofenceTask = geofenceTaskBuilder.Register();
geofenceTask.Completed += (sender, args) =>
{
// MY CODE HERE
};
geofenceTask = geofenceTaskBuilder.Register();
}
I have no other ideas. Any help?
just stumbled upon a similar issue (Visual Studio stopped Debugging when I started the Background Task over the "Lifecyle-Events-Dropdown", stating "BACKGROUNDTASKHOST.EXE' has exited with code 1 (0x1)" in the Console-Output-Window.
Adding the missing reference to my Tasks-Assembly (winmd) Project in the WP8 Project solved it.
The project BackgroundTask need be (windows runtime component).