I'm not able to get ExtendedExecution
to work properly. The problem is that the Revoked
event is not being fired until the execution is finished. If we take a sample:
private async void OnSuspending(object sender, SuspendingEventArgs e)
{
Debug.WriteLine("Suspending in app");
var deferral = e.SuspendingOperation.GetDeferral();
using (var session = new ExtendedExecutionSession())
{
session.Reason = ExtendedExecutionReason.SavingData;
session.Description = "Upload Data";
session.Revoked += (s, a) => { Debug.WriteLine($"Extended execution revoked because of {a.Reason}"); };
var result = await session.RequestExtensionAsync();
if (result == ExtendedExecutionResult.Denied) Debug.WriteLine("Extended execution failed");
else
{
Debug.WriteLine("Executing");
await Task.Run(() => { Task.Delay(9000).Wait(); Debug.WriteLine("Finished the task"); });
Debug.WriteLine("Executing finished");
}
Debug.WriteLine("Suspending after execution");
}
deferral.Complete();
}
The documentation states that Revoked
event should be fired upon resuming the app, but if you try the code with debugger attached, then you will see that debug output seems to look ok, but you have to wait 9000 ms for it to show up. This means that the code is suspended until the session finishes.
The biggest problem is that if you fire this without debugger attached, launch the app, suspend and then resume, you will see a black screen for few seconds and then OS will terminate your app.
Am I missing something? Has anybody got it working correctly?
The usage of the await
and Task
is causing that your continued Task remains on the main thread making you have to wait with the black screen.
Remember that the await
behavior is to schedule the execution into the Dispatcher
, NOT to begin a new thread, NOR schedule its execution into the ThreadPool. As consequence, No more UI messages can be processed until the Delay()
ends.
Just perform your time-consuming operation on a new thread, but ensure to keep the session open until it ends.
Take a look of this https://msdn.microsoft.com/en-us/magazine/jj991977.aspx to get a good insight about how execution is sheduled
There is no UI problem or anything. Your code works. Your expectation is wrong.
Using the ExtendedExecutionSession
you're telling your app you need time to save and it shall not be revoked until you are finished. In your case that takes about 9 seconds.
Try suspending the app, wait for 10 seconds an then revoke it. It will happen immediately.
Then try suspending the app and revoke it before the session is finished. Now the ExtendedExecutionSession
will tell your OS that your app cannot be revoked yet and it has to wait until the saving process is finished. That's what you want.
See Microsoft doc on extended execution:
Requesting a
ExtendedExecutionReason.SavingData
extended execution session while the app is in the Suspending state creates a potential issue that you should be aware of. If an extended execution session is requested while in the Suspending state, and the user requests the app be launched again, it may appear to take a long time to launch. This is because the extended execution session time period must complete before the old instance of the app can be closed and a new instance of the app can be launched. Launch performance time is sacrificed in order to guarantee that user state is not lost.
What's mentioned in the section about "Revoke" is interesting for you, too:
When the
Revoked
event is fired for anExtendedExecutionReason.SavingData
extended execution session, the app has one second to complete the operation it was performing and finish Suspending.
One second is not enough to finish your 9 seconds waiting.
To eliminate the possibility of delayed display of your debug output, you can test it with adding the current time to the output. The OS probably has a problem with the session not closing properly because the 9 seconds don't finish.
Also note the remark on EnterBackground:
Previously your suspending callback was the best place to save state after a user finished a session with your app. However, now an application may continue running in the background and then move back to the foreground due to trigger activity without ever reaching the suspended state. The best place to save data after a user session is in your entered background event handler.
You will probably want to do your code in case the Exiting
event is fired.
For OnSuspending
try doing the waiting with a for loop that breaks (cancelling the saving process) as soon as the revoke happens, only waiting for half a second at a time.
UPDATE:
...Or use a Background Task, as suspending seems to be the only reliable warning before termination:
//
// Declare that your background task's Run method makes asynchronous calls by
// using the async keyword.
//
public async void Run(IBackgroundTaskInstance taskInstance)
{
//
// Create the deferral by requesting it from the task instance.
//
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
//
// Call asynchronous method(s) using the await keyword.
//
var result = await ExampleMethodAsync();
//
// Once the asynchronous method(s) are done, close the deferral.
//
deferral.Complete();
}
UPDATE2:
For the "proper" way how it should be done, see the official example:
private async void OnSuspending(object sender, SuspendingEventArgs args)
{
suspendDeferral = args.SuspendingOperation.GetDeferral();
rootPage.NotifyUser("", NotifyType.StatusMessage);
using (var session = new ExtendedExecutionSession())
{
session.Reason = ExtendedExecutionReason.SavingData;
session.Description = "Pretending to save data to slow storage.";
session.Revoked += ExtendedExecutionSessionRevoked;
ExtendedExecutionResult result = await session.RequestExtensionAsync();
switch (result)
{
case ExtendedExecutionResult.Allowed:
// We can perform a longer save operation (e.g., upload to the cloud).
try
{
MainPage.DisplayToast("Performing a long save operation.");
cancellationTokenSource = new CancellationTokenSource();
await Task.Delay(TimeSpan.FromSeconds(10), cancellationTokenSource.Token);
MainPage.DisplayToast("Still saving.");
await Task.Delay(TimeSpan.FromSeconds(10), cancellationTokenSource.Token);
MainPage.DisplayToast("Long save complete.");
}
catch (TaskCanceledException) { }
break;
default:
case ExtendedExecutionResult.Denied:
// We must perform a fast save operation.
MainPage.DisplayToast("Performing a fast save operation.");
await Task.Delay(TimeSpan.FromSeconds(1));
MainPage.DisplayToast("Fast save complete.");
break;
}
session.Revoked -= ExtendedExecutionSessionRevoked;
}
suspendDeferral?.Complete();
suspendDeferral = null;
}
private async void ExtendedExecutionSessionRevoked(object sender, ExtendedExecutionRevokedEventArgs args)
{
//If session is revoked, make the OnSuspending event handler stop or the application will be terminated
if (cancellationTokenSource != null){ cancellationTokenSource.Cancel(); }
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (args.Reason)
{
case ExtendedExecutionRevokedReason.Resumed:
// A resumed app has returned to the foreground
rootPage.NotifyUser("Extended execution revoked due to returning to foreground.", NotifyType.StatusMessage);
break;
case ExtendedExecutionRevokedReason.SystemPolicy:
//An app can be in the foreground or background when a revocation due to system policy occurs
MainPage.DisplayToast("Extended execution revoked due to system policy.");
rootPage.NotifyUser("Extended execution revoked due to system policy.", NotifyType.StatusMessage);
break;
}
suspendDeferral?.Complete();
suspendDeferral = null;
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With