I have developed several apps for Windows 8 and Windows Phone 8, and I am always amazed my one new feature that was introduced to the C# compiler: async and await.
If you are not familiar with the new async feature of C#, you could try to search for “C# async programming” on the Internet or go to this page on MSDN to read the documentation:
http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx
If you have already used async/await for a while, you must be aware of the danger of async void (aka “fire-and-forget”), which could creates unobserved exception easily. In other words, you cannot simply use a try-catch block to catch the exception if you are awaiting an async void. There are already many blog posts and videos on this topic already, so I will not describe it again here. You can have a look at this video if you want to know more about the problem of async void:
There are other culprits: Task.WhenAny and friends
Async void is problematic, but it can be identified easily, mainly because almost everyone is aware of this now. However, there are more to be aware of!
Recently I have updated one of my Windows 8 app: Project Timeline to make it even more stable, so instead of letting the function CurrentApp.LoadListingInformationAsync run in the background, I awaited for it directly on the splash screen. I then realized that the Store sometimes became very slow and the function LoadListingInformationAsync ran for several minutes and threw an exception. I have handled all the possible exceptions, so it did not crash my app, but forcing the users to look at the splash screen for minutes was just unacceptable! Therefore I had another tweak that could “cleverly” solve the problem (yes I put the double quotes there because it turned out not to be clever at all): I let it wait for maximum 5 seconds and then “release the thread”. I replaced this
1 2 3 4 5 6 7 8 |
try { await CurrentApp.LoadListingInformationAsync(); } catch (Exception ex) { // Report error here } |
by this
1 2 3 4 5 6 7 8 |
try { await Task.WhenAny(Task.Delay(5000), CurrentApp.LoadListingInformationAsync().AsTask()) } catch (Exception ex) { // Report error here } |
The app worked beautifully and exactly as I had expected, until I saw strange unobserved exceptions thrown after the splash screen had disappeared, and those exception were not handled by the above catch block.
Then it becomes so obvious that Task.WhenAny is actually one complicated way of “fire-and-forget”: the execution will wait for your task, but exceptions will not be handled by the containing try-catch, even when the Task throwing the exception finishes first and even when you are not using any async void anywhere here.
The fix for this is very simple: putting the try-catch block inside the task in WhenAny instead of outside. You will still not be able to throw the exceptions outside the function (same as async void), and basically the exceptions will not crash your app (unless you try to handle unobserved exceptions) but you will know better what’s going on by putting a try-catch at the right place.
Conclusion
If you are trying to make the app really stable and work exactly as you want, you must be careful of not only those async void, but also all Task.WaitAll, Task.WaitAny, and Task.WhenAny (Task.WhenAll is not in this league). All those work in a “fire-and-forget” manner, and can mess up which your exception handling plan.
Harsh lesson learned this time!