I am hoping someone can shed some light on a problem I am having. I am writing a Windows Store app which on startup needs to copy a "seed" database from the App installation folder Windows.ApplicationModel.Package.Current.InstalledLocation.Path
to the Local data folder Windows.Storage.ApplicationData.Current.LocalFolder
. To do this I have used the following called from my OnNavigatedTo
method in Main()
.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
//Calls method in Dal Class which ensures database is in correct location and if not copies it
Dal.LoadData();
RefreshScenarioList();
ScenarioList.SelectedIndex = 0; //this is the Default Item
}
public static async void LoadData()
{
await CopyIfNotExists(dbName);
}
private static async Task CopyIfNotExists(string dbName)
{
if (await GetIfFileExistsAsync(dbName) == null)
{
StorageFile seedFile = await StorageFile.GetFileFromPathAsync(
Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path,
dbName));
await seedFile.CopyAsync(Windows.Storage.ApplicationData.Current.LocalFolder);
}
}
private static async Task<StorageFile> GetIfFileExistsAsync(string key)
{
try
{
return await ApplicationData.Current.LocalFolder.GetFileAsync(key);
}
catch (FileNotFoundException) { return default(StorageFile); }
}
Please also note that I have also used GetFileAsync
and also tried using a uri to obtain the seed file (to see if there was any difference). With the combination here I seem to be having the highest success rate. This seems to work 2-3 times out of every 4 tries but I would be much more comfortable with something that works 100% of the time. It only needs to copy the database once on startup.
When it doesn't work it "seems" to never return (ie CopyIfNotExists
never seems to complete). The issue appears to be in the CopyIfNotExists
method. The problem is that virtually the next line of code in my OnNavigatedTo
method is a query on the database and it falls over when the database doesn't copy.
Any insights that anyone may have would be greatly appreciated.
From what I can see, the problem here is that your use of async
and await
does not go as far up the call stack as it can.
When writing async code, there are a couple of general rules to follow -
Foo
uses the async
keyword, then any other function that calls Foo
must also use the async
keyword.async void
in a function signature - use async Task
instead. The exceptions are:
void
Let's take a look at the method signatures on your call stack. OnNavigatedTo
is shown here at the top:
override void OnNavigatedTo()
async void LoadData()
async Task CopyIfNotExists()
async Task<StorageFile> GetIfFileExistsAsync()
The bottom two methods of the call stack uses async Task
in the method signatures. Above those on the call stack is LoadData
, which follows rule 1 above, but breaks rule 2. LoadData
should be changed to this:
public static async Task LoadData()
{
await CopyIfNotExists(dbName);
}
Next we try to make OnNavigatedTo
follow the above rules:
async protected override void OnNavigatedTo(NavigationEventArgs e)
Note that we cannot use async Task
, since your method is an override. We have to stick to async void
.
Now you just need to add await
to your LoadData
call since it is an async
method. We do this so that your program waits for LoadData
to complete prior to calling RefreshScenarioList
:
async protected override void OnNavigatedTo(NavigationEventArgs e)
{
//Calls method in Dal Class which ensures database is in correct location and if not copies it
await Dal.LoadData();
RefreshScenarioList();
ScenarioList.SelectedIndex = 0; //this is the Default Item
}
Now you will want to also ensure that the call stack for RefreshScenarioList
follows the async rules above.
One more thing that I noticed with your code. In Windows 8.1 or later, you should use TryGetItemAsync instead of GetFileAsync
:
private static async Task<StorageFile> GetIfFileExistsAsync(string key)
{
return await ApplicationData.Current.LocalFolder.TryGetItemAsync(key) as StorageFile;
}
The new method returns null
when it cannot find a file. The old method throws an exception. Throwing exceptions on a UI thread can cause your UI to stall, so it is advantageous to use the new method.