“We are on the path to Windows and Windows Phone Convergence” (//Build 2012 – How to Leverage your Code Across WP8 and Win8, Slide 6)
I often hear people saying the phrase “Windows 8 Phone” when they are talking/asking about “Windows Phone 8”. Throughout presentations that I’ve given and other discussions I’ve had over the past several months that covered the topic of Windows Phone 8, I’ve made it a point to emphasize that Windows Phone 8 is NOT a Windows 8 Phone. By doing so I’m not trying to be a nit-picky jerk, but rather I’m trying to underscore that there are important differences – both obvious and subtle – between the Windows Store App and Windows Phone platforms, and there are some major pitfalls that developers can stumble into if they are not aware of the way that some of the key platform technologies work. One situation where these nuanced platform differences comes to light is in differences in the proper use of asynchronous operations within the various lifecycle events exposed by these two platforms; specifically the events pertaining to suspension/deactivation and application closing.
Huh? To put it simply, the APIs available to Windows Store App development include tools that developers can use to safely(*) include asynchronous method calls when their application is being suspended. The Windows Phone 8 APIs that support Deactivation and Closing do not include these tools, and it can be tricky to notice the problems that can arise.
OK… time for some background which should serve to highlight the problem. There are 2 general areas to cover. First, there are the application lifecycles that are exposed by Windows Store and Windows Phone 8 apps. Second, some background as to the nature of the new async/await-based asynchronous programming model needs to be provided.
The Windows Phone and Windows Store Application Lifecycles
Both Windows Phone and Windows Store apps (hereafter WP and WS) feature a lifecycle that is a bit different from that of a traditional desktop application. While desktop apps are allowed to constantly run (and make unfettered use of precious device resources), the execution lifetime of WP/WS apps is primarily(**) limited to only when the app is currently in the foreground. In WP, there’s only one app running in the foreground at a given time, where in WS there are currently at most two, depending on whether one is running in Snapped View. When the apps are either being closed or losing their foreground status, events are provided which allow program code to execute as a result. Note that while this code can delay the related app lifecycle event from completing, they cannot cancel/prevent it – there is no way to prompt the user to ask “are you sure you’d like this to happen? Yes/No?” In fact, there’s also the notion of a “death clock” – the apps have a limited amount of time to execute their cleanup code, or the OS will simply kill the app. With this in mind, it is worth giving strong consideration in WP/WS apps to a “save as you go” approach over the more traditional “save at the very end” approach that was quite common in Desktop apps.
As a simple overview, in WP apps, when a user brings up a different app through the use of one of the device hardware buttons or by responding to a Toast notification or a Reminder, or by switching to another application via a file or protocol association (or one of several other ways), the app enters a dormant state. On its way to this state, the OS gives the app an opportunity to handle a Deactivated event. The app then has 10 seconds to complete any operations, and once execution of this handler has completed, the OS then moves the app along to this end-state. Likewise, when an app is closed by using the Back button from the first page in the app’s backstack, the app can elect to handle a Closing event. Note that the OS may decide to exit a running application that has been deactivated, either due to device memory pressure or if the app is relaunched from the Start or Tile screens, in which case the Deactivated event has been called, but since the app is already dormant, the Closing event will not be called. (App Activation & Deactivation for Windows Phone)
Similarly, in WS apps, when a user brings up a different app through a contract activation (including the Launch contract which occurs when a Tile is clicked form the Start screen), the OS invokes the app-losing-focus’s Suspending event. This event is also raised if the app is being explicitly closed by dragging/swiping from the top of the screen to the bottom, or the OS is rebooting, etc. There is no “dedicated” close method that will be called. (Windows Store App Lifecycle Overview)
In either case, these event handler methods offer the last opportunity for an app to persist user state information to disk, potentially to be re-accessed the next time the app is activated/launched/awakened/etc. Once either the app completes processing these methods or the aforementioned “death clocks” expire, control is returned to the OS to complete its suspension/shutdown operations. (This last sentence is very important.)
The Task Based Asynchronous Pattern and the Async/Await Keywords
One of the core concepts that applies to development with the new Windows Runtime APIs (WinRT) is that any API call that *could* potentially take more than 50ms to complete has been made asynchronous, with no synchronous equivalent. Among other consequences, this continues a trend first seen in other Microsoft APIs of ensuring that developers do not create UI’s that lock up while some long-running operation is underway, and if managed correctly, can make applications seem more responsive than ever before. However, developers who had previously not delved into the nuances of asynchronous programming are now required to do so. To that end, the async and await keywords added to the C# language offer substantial help, but it is still important to understand what’s going on “under the hood.”
A detailed description of how the compiler reacts to async/await in code it encounters is beyond the scope of this article, and a great explanation is available in Chapter 28 of Jeff Richter’s book “CLR Via C#, 4th Edition”. To put it simply, when the compiler encounters an async function, behind-the-scenes it generates a state machine and rearranges the code into various states the state machine manages. The state machine waits for code marked with “await”, and when that code signals as “complete”, the subsequent code is executed, etc. This is the “magic” that allows the code to be written linearly, while still respecting the asynchronous execution.
The following class shows one of the simplest implementations I could think of:
In this case, the compiler loosely interprets the code as the following:
- Start Task.Delay for 1000ms (on another thread), and when it completes, execute DoSomethingElse().
- Then signal the Task (implied if return type is void) that execution has completed.
- If the return type is Task<T>, the function’s return type will be used as the T value.
- While that is going on, go ahead and return control to the calling function
OK…so what’s the big deal? Some readers may have figured it out from the background – don’t worry if you haven’t – some very competent developers have fumbled this pretty badly. The crux of the problem lies when you merge the concept of the applications’ lifecycle closing events with the execution of asynchronous code. Remembering the sentence I called attention to earlier regarding the processing of the Deactivated/Closing events (WP) or the Suspending event (WS):
Once either the app completes processing these methods or the aforementioned “death clocks” expire, control is returned to the OS to complete its suspension/shutdown operations.
But if these methods are made asynchronous using async, the compiler will generate a state machine, moving the execution from the first await forward into alternate threads and returning to the calling method at that point….at which time, the OS will proceed with its suspend/shutdown operations, blithely ignoring whatever may be going on in those other threads. If the asynchronous method in question happens to be a file-save, congratulations! You may have just saved half a file! Or more…or less. (Have you heard the one that goes “Why did the multithreaded chicken cross the road? To other side get to the. Ask me again…”)
This can be seen with the following WP code:
And the following WS code:
In the examples above, the DumpText function may start outputting content to the Debug Window, though it is likely it won’t even get to the first write, and highly unlikely that it will get all the way to 10.
But it Works in Windows Store Apps…
This is partially true. WS apps introduce the concept of “deferrals” at critical places where the OS and asynchronous operations may possibly collide – and it just so happens that suspension is one such critical place. A deferral can be requested from the SuspendingEventArgs that are part of the event handler signature by using the GetDeferral method. When a deferral is requested, the OS will wait for the deferral instance’s Complete method to be called before it proceeds with its normal suspension/shutdown operations***. (Shameless self promotion alert: I discuss this in the “Application Suspension” of Chapter 3 – Application Life Cycle and Storage in my book “Windows Store Apps Succinctly.”) This process is illustrated in the following code, which will successfully get all the way through the desired 10-count:
Great! Now all we have to do is be sure to use deferrals in our Windows Phone Deactivated and Closing event handlers and we’re all set! There’s just one small problem…the DeactivatedEventArgs and ClosingEventArgs provided by those methods have no notion of deferrals…for that matter, WP8 doesn’t have any such notion as a whole. Remember – we are on a path to convergence…we have yet to achieve said convergence.
So what are our options in this situation?
- Use the synchronous WP8 APIs. In the case of storage, the IsolatedStorage APIs that are part of the WP7 SDK are still available in WP8. Since these calls are synchronous, it avoids the whole “I’ve returned from the method but am not done with my work” issue that is at play. However, for code-reuse/compatibility, it isn’t exactly an ideal option (though the lifecycle events are different enough between the platforms that this may not be a big deal) and in general, it feels like a bit of “looking backwards.”
- Though it has been mentioned before, it should be repeated for completeness – it may not be ideal to “wait until done” to persist content to disk, if that is what the desired async operation involved here happens to be. It may be better to pursue an approach that involves “save as you go.”
In the end, I hope that I have illustrated that it is quite important that developers be aware of the nuances of asynchronous code and the problems than can occur if asynchronous code is used in the exit-oriented event handlers in Windows Phone apps. Ultimately, the simplest guidance is to not make these handlers asynchronous until such a point that the Windows Phone API is given its own mechanism – similar to that afforded to the Windows Store API or otherwise- for handling asynchronous calls in its exit handlers.
While async & await are powerful additions to our developer toolboxes, but “with great power comes great responsibility.” It is important to understand their true functionality. A few resources that can aid in such an understanding include:
- Jeff Richter’s afore-mentioned book – “CLR Via C#, 4th Edition”.
- Stephen Cleary’s recent MSDN Magazine article – “Async/Await: Best Practices in Asynchronous Programming”
- Parallel Programming with .NET blog – http://blogs.msdn.com/b/pfxteam/
- Stephen Toub’s article – ‘“Invoke the method with await”…ugh’ deserves special mention.
* I say “safely” with some trepidation – there are a lot of factors that can impede any perceived “safety” when it comes to executing code in these quasi-shutdown methods, and the nature of asynchronous code inherently adds complexity to the number and nature of possible things that can go wrong.
** Another “weasel word” – there are circumstances where these platforms do allow apps (or some limited portion of the app) to run, such as Background Tasks/Agents.
*** Note that deferrals do not stop the “death clock”…the method can still time-out even if a deferral has been requested. The only thing the deferral does is prevent the normal course of completion signaling that otherwise takes place when the method returns to its caller.