Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extended execution not working properly?

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?

like image 857
Romasz Avatar asked Mar 19 '16 19:03

Romasz


2 Answers

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

like image 97
Jairo Andres Velasco Romero Avatar answered Nov 16 '22 00:11

Jairo Andres Velasco Romero


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 an ExtendedExecutionReason.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;
    });
}
like image 1
Neepsnikeep Avatar answered Nov 16 '22 00:11

Neepsnikeep