When I started writing Windows 8 apps, one of my first productions was wrapping file operations in a helper class called StorageHelper. This class has successfully been leveraged by thousands of Windows 8 developers. I certainly use it every time I interact with a File or a Setting.
But the StorageHelper isn’t perfect in every scenario. For most mainstream applications it is perfect. But for some edge cases where file operations occur in parallel, an asynchronous read or write may not complete before another operation is attempted on the same file. This causes parallel locking. Hans Windhoff, a community developer in Colorado, took my StorageHelper and updated it with the solution – solving locks with some smarts and voodoo.
Here’s Hans’ experience:
When I was writing my first Windows 8 app store application, I was also first using the C# async/await in .Net 4.5.
Asynchronous programming keeps the application user interface (UI) responsive. Async/await makes asynchronous programming simple to write, and easy to read. But in some scenarios a re-entrance situation arises; you are not normally confronted with these in synchronous programming.
File operations are a excellent examples of re-entrance; particularly when executed from within event handlers. When handling UI events, like a button click, it is common to disable the button during the operation. This pattern works fine as long as there is no other button that will call the same code or uses the same resources, like a file.
One problem calling things asynchronously is that while awaiting a completion, other stuff may happen; you may not know what that other stuff will be.
A situation I came across is this:
- A Windows app store application has two frames.
- There is no given sequence in which these frames are called.
- Both frames need some data that comes from a file.
- The data needs to be written back into that file when the frames close (are navigated from).
- Both frames use the same data and therefore the same file.
Typically, I would put the loading and saving of the data into OnNavigatedTo/From event handlers for each frame. In the pre-async/await times, if you don’t worry about a “little” delay, putting these calls directly into the event handlers is no problem.
But if the file access takes time, that delay becomes an issue. Since Windows 8 app store apps do not support synchronous file access functions, the developer is forced (by design) into async patterns, keeping the UI responsive; the event handlers become asynchronous.
The signature of the event handlers is void. If the event handlers are to be awaited, they become - what Jerry Nixon calls - “FireAndForget”. They return control when the first await is hit. However, when navigating from one frame to the other, saving and loading may overlap and throw an ACCESSDENIED exception.
In the pre-async/await days you could use a thread or task. Windows 8 file operations are natively asynchronous. Where a lock might have been the solution to handle multithreading conflicts, locks are not supported in an awaited operation. Also a lock would not work in a re-entrance scenario, because the lock is still on the same thread (the UI thread in the case of event handlers).
To get around this problem I used the AsyncLock from Stephen Toub (Building Async Coordination Primitives, Part 6: AsyncLock) to lock a block around any file access StorageHelper perform. Please note that my contribution here is rather small.
First I bring in the two classes from Stephen Toub’s articles:
- AsyncSemaphore
- AsyncLock
Then in Jerry Nixon’s StorageHelper class I add a member:
This is my only lock that I then use to synchronize all file access.
Writing a file:
Reading a file:
The using also takes care about the possibility of an exception being thrown, so the AsyncLock m_lock will be released if this should happen. Please note that disabling parts of the UI in both frames may still be needed because – as already outlined above – the OnNavigatedTo event handler becomes “FireAndForget” when async and await are added to the definition. So as long as the fired and forgotten event handler has not done its work, the data it is supposed to read, may not be consistent.
Hans Windhoff is a Microsoft developer in Colorado. Find him here. |