I'm writing an WP8 app, and wanna save some data when app is deactivated or closed.
I've tried the new WinRT api, but not working while app is being deactivated :|
The code:
public async Task CacheData()
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
string data = "Hello!";
///WinRT not Working !!!
// Get a reference to the Local Folder
// Create the file in the local folder, or if it already exists, just replace it
StorageFile storageFileIsolated = await localFolder.CreateFileAsync("Data.data", CreationCollisionOption.ReplaceExisting);
Stream writeStream = await storageFileIsolated.OpenStreamForWriteAsync();
using (StreamWriter writer = new StreamWriter(writeStream))
{
await writer.WriteAsync(data);
}
}
}
but working fine with the old api
string data = "Hello!";
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream rawStream = isf.CreateFile("Data.store"))
{
StreamWriter writer = new StreamWriter(rawStream);
writer.WriteLine(data); // save the message
writer.Close();
}
}
I don't know what the problem is :| !!!
I tested it with this code
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
ViewModel.CacheExchangeRates().Wait();
System.Diagnostics.Debug.WriteLine("deactivated !!!!!");
}
never printed deactivated !!!
The problem is with await
keyword. In short, await
really doesn't block the executing thread, as it may sound. Quite the opposite, when execution comes to await
keyword, it looks like return
from the caller's point of view. The continuation (what comes after await
keyword) is scheduled to run later, when the task associated with await
is completed. For more details about that, I encourage you to read more about await
and Task, starting, for example, here.
In your case, when Deactivated or Closing is called, you probably make a call similar to:
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
CacheData();
}
When execution comes to the first await
keyword (that is, to the line StorageFile storageFileIsolated = await localFolder.CreateFileAsync("Data.data", CreationCollisionOption.ReplaceExisting);
), your CacheData()
method returns control to the caller. Everything else will run later, when that task is completed. But you're not waiting for that in your Application_Deactivated
method. Instead, execution continues and control leaves the Application_Deactivated
.
When control leaves the Application_Deactivated, your app is considered to be ready to shutdown. But in fact it's not ready, because await writer.WriteAsync(data);
is probably not even started yet. That's why your app closes without writing anything.
Btw, if your app is not tombstoned during deactivation, as if you return to it quickly, the WriteAsync
should be completed eventually. But of course it's not what you expect.
In order to fix this, you need to run your file write operation synchronously. By the way, this is precisely what old API does, and that's why it works. The easiest way to fix your problem would be to change your CacheData
function to return Task
instead of void
(to public async Task CacheData()
), and use Task.Wait()
in your Application_Deactivated
handler. Like this:
public async Task CacheData()
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
string data = "Hello!";
///WinRT not Working !!!
// Get a reference to the Local Folder
// Create the file in the local folder, or if it already exists, just replace it
StorageFile storageFileIsolated = await localFolder.CreateFileAsync("Data.data", CreationCollisionOption.ReplaceExisting);
Stream writeStream = await storageFileIsolated.OpenStreamForWriteAsync();
using (StreamWriter writer = new StreamWriter(writeStream))
{
await writer.WriteAsync(data);
}
}
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
CacheData().Wait();
}