locked
Odd result with saving file in WinJS.Application.oncheckpoint

    Question

  • Hi, 

    I need to save some status to files in local folder when my app is closed.

    I choose to do that in WinJS.Application.oncheckpoint. But I find I can only get correct result in about 50% of time. In 30% of time it doesn't save at all and in 20% time, I get files(all of them!) with size 0.

    My saving codes are quite fast, I have a separated button to test them and they can be surely finished in 10~30ms. This problem doesn't exist if I run app from Visual Studio in debug mode.

    Any suggestions? 

    Thank you very much.

    cheers

    Alex


    woodhead is as woodhead does

    Monday, June 04, 2012 10:43 AM

Answers

  • What's happening is that the async function you're starting within checkpoint isn't necessarily completing before the app gets suspended. The basic design is that an app can be suspended anytime after you return from your event handler (such as checkpoint), so the behavior you're see isn't at all surprising.

    For this reason, you need to tell Windows that you're doing an async operation so it will defer suspend until that operation is complete (though you are still subject to a 5 second timeout regardless).

    To do this, you have an args.setPromise method inside the checkpoint handler. From the looks of your code, I think you can just place the call to createFileAsync inside setPromise (including the .then and the call to writeTextAsync). Since you're returning the promise from writeTextAsync, the promise chaining should work.

    .Kraig

    P.S. The top-down drag gesture to close an app will trigger suspend, but there is a short time in there, as you've discovered, that if you launch the app again it doesn't go through the same routine. See the differences between previousExecutionState of notRunning and closedByUser on http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.activation.applicationexecutionstate.aspx and http://msdn.microsoft.com/en-us/library/windows/apps/hh464925.aspx.

    If there's data you really must have in all these cases, see if there's a place you can save it incrementally (when it changes) rather than waiting for checkpoint. This is really what you should do with app settings that you expect to always be there whenever you restart the app. Checkpoint is best for saving session state specifically for rehydrating the app if it's restarted after being terminated (previousExecutionState==terminated).

    Monday, June 04, 2012 4:13 PM

All replies

  • Please post your code

    Jeff Sanders (MSFT)

    Monday, June 04, 2012 12:16 PM
    Moderator
  • Hi Jeff,

    Thanks for the reply.

    Here is my code:

    default.html:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>SaveTest</title>
    
        <!-- WinJS references -->
        <link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet" />
        <script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
        <script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>
    
        <!-- SaveTest references -->
        <link href="/css/default.css" rel="stylesheet" />
        <script src="/js/default.js"></script>
    </head>
    <body>    
        <span id ="pmt"></span>
    </body>
    </html>

    default.js

    // For an introduction to the Blank template, see the following documentation:
    // http://go.microsoft.com/fwlink/?LinkId=232509
    (function () {
        "use strict";
    
        var app = WinJS.Application;
        var activation = Windows.ApplicationModel.Activation;
        WinJS.strictProcessing();
        
        var status = Math.round(Math.random() * 10000);
        app.onactivated = function (args) {
            if (args.detail.kind === activation.ActivationKind.launch) {
    
                args.setPromise(WinJS.UI.processAll());
    
                //show status value.status is a number that changed every time when the app is launched
                document.getElementById("pmt").innerText = "status value:" + status + "\r\n";
                var applicationData = Windows.Storage.ApplicationData.current;
                var localFolder = applicationData.localFolder;
                localFolder.getFileAsync("test.txt")
                    .then(function (file) {
                        return Windows.Storage.FileIO.readTextAsync(file);
                    })
                    .then(function (s) {
                        //show the text from file. 
                        //Sometime we may get empty string!!
                        document.getElementById("pmt").innerText += "\r\n" + s;
                    });
            }
        };
    
        var text = "";
        //increase the text size will make it easier to reproduce the problem
        //repeat 1000 times will get a string with length about 43k
        for (var i = 0; i < 1000; i++)
            text += "the quick brown fox jumps over the lazy dog ";
    
        app.oncheckpoint = function (args) {
            
            //save the time at the first line to distinguish each of saving 
            var content = new Date().toString() + "\r\n" + text;
    
            var applicationData = Windows.Storage.ApplicationData.current;
            var localFolder = applicationData.localFolder;
            localFolder.createFileAsync("test.txt", Windows.Storage.CreationCollisionOption.replaceExisting)
                        .then(function (file) {
                            return Windows.Storage.FileIO.writeTextAsync(file, content);
                        });
        }
        app.start();
    })();

    This code is very simple: I save a long string to a file then read it out when app is launched next time. 

    The way I test this app is:

    1.Launch then drag the top of app to bottom to close the app

    2.Wait until the app is closed (Disappear from Task manager)

    3.Launch app again. If every thing is all right, I would be able to see a status number, last saving time and a lot of text. If something is wrong, there would be no text except the status number.

    4.keep doing this test circle for many times.

    According to my test, I get empty text pretty often on my desktop and a few times on my tablet.

    Here is another question:

    After I close the app by dragging the top of app to bottom to close the app, I need to wait for a few seconds to receive oncheckpoint event to save my file. However, if I launch app again during that shot time, system will start a new instance of app while terminate the previous instance without firing its  oncheckpoint event. Then l LOST all of my change.

    What should I do to save the change to file this scenario?

    Again, thank you very much.

    cheers

    Alex


    woodhead is as woodhead does


    • Edited by Alex ZS Monday, June 04, 2012 2:01 PM
    Monday, June 04, 2012 1:59 PM
  • What happens if you let the app suspend and terminate naturally?  Does forcing a close "by dragging the app to the bottom" cause the app to go through the suspend state?

    ***Update:  Sorry, I was mistaken.  According to the Application Lifecycle document, a forced close should go through the suspend state if it is registered.

    • Edited by jrboddie Monday, June 04, 2012 3:50 PM
    Monday, June 04, 2012 2:28 PM
  • What's happening is that the async function you're starting within checkpoint isn't necessarily completing before the app gets suspended. The basic design is that an app can be suspended anytime after you return from your event handler (such as checkpoint), so the behavior you're see isn't at all surprising.

    For this reason, you need to tell Windows that you're doing an async operation so it will defer suspend until that operation is complete (though you are still subject to a 5 second timeout regardless).

    To do this, you have an args.setPromise method inside the checkpoint handler. From the looks of your code, I think you can just place the call to createFileAsync inside setPromise (including the .then and the call to writeTextAsync). Since you're returning the promise from writeTextAsync, the promise chaining should work.

    .Kraig

    P.S. The top-down drag gesture to close an app will trigger suspend, but there is a short time in there, as you've discovered, that if you launch the app again it doesn't go through the same routine. See the differences between previousExecutionState of notRunning and closedByUser on http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.activation.applicationexecutionstate.aspx and http://msdn.microsoft.com/en-us/library/windows/apps/hh464925.aspx.

    If there's data you really must have in all these cases, see if there's a place you can save it incrementally (when it changes) rather than waiting for checkpoint. This is really what you should do with app settings that you expect to always be there whenever you restart the app. Checkpoint is best for saving session state specifically for rehydrating the app if it's restarted after being terminated (previousExecutionState==terminated).

    Monday, June 04, 2012 4:13 PM
  • Hi Kraig,

    Thanks for your answer. That answered all of my questions. I'll have my app saving data incrementally.

    However, I really don't understand, why can't you just simply bring up the background app when user try to launch it before it is really terminated?

    Comparing with incrementally saving, the saving code can be finished in 5 sec is much easier to implement. All we need is just a reliable oncheckpoint event.

    Thanks again.

    cheers

    Alex


    woodhead is as woodhead does

    Tuesday, June 05, 2012 6:04 AM
  • If the user explicitly closes an app with a close gesture or Alt+F4, then it's not, to their mind, "in the background" even though that's where it is technically. So if you relaunch the app again, the user won't expect it to come up where they'd left off, but rather in a default state. For example, if you were viewing personal information in the app, then closed it explicitly because someone had entered the room, then restarted it, you wouldn't want that same app state to pop right back up like it would if you'd simply put the app in the background. In other words, the app start on re-launch needs to honor the fact that the user did an explicit action to close.

    The difference between the previous state of notRunning (within 10 seconds of explicit close) and closedByUser (after 10 seconds) if subtle. Generally speaking, apps treat them identically. However, the latter indicates that the user closed the app and left it closed for some time, as opposed to restarting it. This might be used to refresh some cached information or other time-sensitive data, if that's important to the app.

    Tuesday, June 05, 2012 3:02 PM