Martin Suchan – BloQ Random #WPdev stuff

27Jul/145

File IO Best Practices in Windows and Phone apps – Part 1. Available APIs and File.Exists checking

Working with files is one of the most common tasks when developing any Windows Phone or Windows apps. In this mini-series of articles I'll show you available APIs in all Windows Phone and Windows versions, caveats when doing task such as checking if specific file exists, getting target file, reading writing data from/to files and also how to effectively extract data from ZIP files, and more.

In the first part of this series I'll discuss available File IO APIs in Windows Phone 8, 8.1 Silverlight, 8.1 XAML, Windows 8 and 8.1 platforms and benchmark all available methods for testing, if target file in Local Folder exists, or not. To my great surprise, the performance of methods in StorageFolder API is dramatically different across different platforms and it's not a good idea to use single approach everywhere.

Available File IO APIs

Windows.Storage.StorageFile and StorageFolder API

For working with files in Windows and Windows Phone apps the most common API is the StorageFile and StorageFolder API from the new Windows Runtime. Here the StorageFile intance represents file and the StorageFolder, well, a folder on the filesystem. Each StorageFolder instance contains method for accessing child files and folders, creating new files, deleting, etc.

In Windows Phone or Windows application it's possible to access selected folders on the filesystem:

  • ApplicationData.Current.LocalFolder for storing local application data
  • Package.Current.InstalledLocation for read-only accessing files within the application installation directory

On "8.1" platforms it's also possible to access these additional locations:

  • ApplicationData.Current.RoamingFolder for storing data shared using OneDrive across devices
  • ApplicationData.Current.TemporaryFolder for storing temporary data
  • ApplicationData.Current.LocalCacheFolder for storing local data, that are not back-uped into cloud, when reinstalling the phone (WP8.1 XAML only)
  • KnownFolders.DocumentsLibrary, HomeGroup, MediaServerDevices, MusicLibrary, PicturesLibrary, RemovableDevices and VideosLibrary for accessing and storing files of specific type. Note not all of these folders could be accessed from all "8.1" platforms

It's also possible to access other files in special scenarios using various contracts, but this won't be covered now.

For accessing files within these folders we can use one of several methods:

  • folderInstance.GetFileAsync(string fileName) - fileName could be either single file name "myfile.txt", or path to a file in nested folder in backslash separated format: "dirA\dirB\myfile.txt". Note the backslash format is actually not much documented on MSDN, but it works and it makes the StorageFolder API much easier to use.
  • StorageFile.GetFileFromPathAsync(string filePath) - specific file can be accessed using full file path without need for getting the parent StorageFolder first. The Path must be in backslash separated format: "C:\Data\Users\DefApps\AppData\{ECCB0535-E89C-4C9B-A223-DD34591CDC5E}\local\file.txt". You can get this full path easily by combining Path of the parent folder and path of the file:
    string rootPath = ApplicationData.Current.LocalFolder.Path;
    string filePath = Path.Combine(rootPath, filename);
  • StorageFile.GetFileFromApplicationUriAsync(Uri fileUri) - similar method to the previous one, you don't need the parent folder to access the file using this method. The Uri for this method is expected in one of several file Uri scheme formats. In short, for accessing files in LocalFolder we use "ms-appdata:///local/myfile.txt" and for accessing file in app installation folder we use "ms-appx:///myfile.txt" uri. The benefit here is, that we don't need to know the parent folder path to construct the Uri, the bad thing is we can only access some locations using this Application Uri format - Local, Roaming, Temporary, LocalCache folder and app installation folder.

You'll probably want to use this API in all your projects, but beware, the StorageFolder API has some methods and properties in Windows Phone 8 that are throwing NotImplementedException, so always check first the description and metadata before using such methods in WP8 apps.

System.IO.IsolatedStorage.IsolatedStorageFile API

The IsolatedStorageFile API made its way to WP7 - WP8.1 Silverlight platforms from the original .NET through Silverlight web platform and it's no longer available in Windows apps or in Windows Phone 8.1 XAML apps. This API is also completely synchronous with no async methods available, which might be useful or problematic in some scenarions.

Files and folders are not represented by any object in this API, but are instead accessed directly using methods like IsolatedStorageFile.OpenFile(path, mode) returning IsolatedStorageFileStream object for accessing the file content.

Since this API is not available in newer project types, only in WP Silverlight projects, I won't go into any more details. It should be probably only mentioned, that if you have to develop Windows Phone app supporting the Windows Phone 7.5 as well, you need to use this API, because StorageFolder is not yet available in the WP7.5 platform.

System.IO.File and Directory API

Similarly to IsolatedStorageFIle API, the File and Directory API is also available only in WP7 - WP8.1 Silverlight projects. It's really straightforward and it also uses full file paths for creating/opening/deleting files instead of objects representing each file or folder.

This API is not much useful by itself, because it does not give us any starting point - LocalFolder path or path of any other folder we can use, but it is really useful for some specific scenarios I'll mention later.

How to test if specific file exists

When working with files in application, checking if target file exists is one of most common tasks. Typical scenario:

if (!FileExist("myfile.txt")) { DownloadFile("myfile.txt"); }
DoStuff();

(Un)fortunately there is quite a lot of ways, how to test on each platform, if specific file exists or not, and the problem is that the performance of these methods can be dramatically different, so choosing the right one for you platform is really crucial.

These are all available methods, that we can use for checking, if target file exists, with platforms where these methods can be used:

table

 

 

 

 

 

 

Let's see, how each of these methods could be used for testing, if in LocalFolder\pictures folder exists mypicture.png file:

File.Exists

File.Exists is a static, synchronous method returning bool, accepting either relative path or full file path. It's available only in Silverlight-based Windows Phone projects.

string fileName = "pictures\\mypicture.png";
string parentPath = ApplicationData.Current.LocalFolder.Path;
string filePath = Path.Combine(parentPath, fileName);
bool fileExists = File.Exists(filePath);

IsolatedStorageFile.FileExists

IsolatedStorageFile.FileExists is an instance synchronous method returning bool, accepting relative file path. It's available only in Silverlight-based Windows Phone projects.

string fileName = "pictures\\mypicture.png";
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
bool fileExists = file.FileExists(fileName);

StorageFile.GetFileFromPathAsync + Exc

StorageFile.GetFileFromPathAsync is a static asynchronous method returning StorageFile instance, if the file exists, or throwing FileNotFoundException otherwise. It accepts full file path as a parameter and it's available in all our target platforms.

bool fileExists;
try
{
    string fileName = "pictures\\mypicture.png";
    string parentPath = ApplicationData.Current.LocalFolder.Path;
    string filePath = Path.Combine(parentPath, fileName);
    StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
    fileExists = file != null;
}
catch (Exception e)
{
    fileExists = false;
}

StorageFile.GetFileFromApplicationUriAsync + Exc

StorageFile.GetFileFromApplicationUriAsync is similar to the previous method, it's a static asynchronous method returning StorageFile instance, if the file exists, or throwing FileNotFoundException otherwise. It accepts file Uri in a new Uri scheme format as a parameter and it's available in all our target platforms.

bool fileExists;
try
{
    string fileName = "pictures/mypicture.png";
    Uri appUri = new Uri("ms-appdata:///local/" + fileName);
    StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(appUri);
    fileExists = file != null;
}
catch (Exception e)
{
    fileExists = false;
}

StorageFolder.GetFileAsync + Exc

StorageFolder.GetFileAsync is an instance asynchronous method returning StorageFile instance, if the file exists, or throwing FileNotFoundException otherwise. It is called on parent folder and it accepts either simple file name or file path to nested file as a parameter. It's available in all our target platforms.

bool fileExists;
try
{
    string fileName = "pictures\\mypicture.png";
    StorageFile file = await root.GetFileAsync(fileName);
    fileExists = file != null;
}
catch (Exception e)
{
    fileExists = false;
}

StorageFolder.GetFilesAsync + iteration

StorageFolder.GetFilesAsync is an instance asynchronous method returning collection of all existing StorageFiles within the parent folder. If the target file is in this collection, then it exists. It's available in all our target platforms.

bool fileExists;
try
{
    string folderName = "pictures";
    StorageFolder pictures = await ApplicationData.Current.LocalFolder.GetFolderAsync(folderName);
    string fileName = "mypicture.png";
    IReadOnlyList<StorageFile> files = await root.GetFilesAsync();
    StorageFile file = files.FirstOrDefault(f => f.Name == filename);
    fileExists = file != null;
}
catch (Exception e)
{
    fileExists = false;
}

StorageFolder.TryGetItemAsync

StorageFolder.TryGetItemAsync is an instance asynchronous method returning IStorageItem instance, that can be cast to StorageFile or StorageFolder, if the file or folder exists, or null otherwise. It is called on parent folder and it accepts either simple file name or file path to nested file as a parameter. It is only available in the Windows 8.1 API, that was released in Fall 2013, but for some unknown reason it's not available in newer Windows Phone 8.1 API, that was released in Spring 2014.

string fileName = "pictures\\mypicture.png";
StorageFolder parent = ApplicationData.Current.LocalFolder;
StorageFile file = await parent.TryGetItemAsync(fileName) as StorageFile;
bool fileExists = file != null;

Update, added new sync methods!

After publishing the original version of my article I got an idea to test Window.Storage methods again, but this time without the async/await construct, in a fully synchronous way. These are the modified methods:

StorageFile.GetFileFromPathSync + Exc

The only difference here is that GetFileFromPathAsync method is not called using the await construct, but rather the results is acquired synchronously using this method chain:
.AsTask().ConfigureAwait(false).GetAwaiter().GetResult()
This converts the original returned Windows Runtime object IAsyncOperation<StorageFile> into CLR Task<StorageFile>, this Task is then configured to not continue on captured synchronization context when it's finished (to prevent deadlocks on UI thread), the awaiter for the Task is again acquired and the result is returned synchronously. The benefit here is that we don't have to await the generated code and that the generated IL is therefore simpler. But the problem might be that the method is now blocking the calling thread and also that the code after this line is  NOT executed on the original synchronization context. In other words if you are checking if file exists on UI thread, you use this Sync method and then you want to update any UI Control, you have to use the Dispatcher to execute the changes.

bool fileExists;
try
{
    string fileName = "pictures\\mypicture.png";
    string parentPath = ApplicationData.Current.LocalFolder.Path;
    string filePath = Path.Combine(parentPath, fileName);
    StorageFile file = StorageFile.GetFileFromPathAsync(filePath).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
    fileExists = file != null;
}
catch (Exception e)
{
    fileExists = false;
}

StorageFile.GetFileFromApplicationUriSync + Exc

We have used the same modification here as well, making the method synchronous.

bool fileExists;
try
{
    string fileName = "pictures/mypicture.png";
    Uri appUri = new Uri("ms-appdata:///local/" + fileName);
    StorageFile file = StorageFile.GetFileFromApplicationUriAsync(appUri).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
    fileExists = file != null;
}
catch (Exception e)
{
    fileExists = false;
}

StorageFolder.GetFileSync + Exc

We have used the same modification here as well, making the method synchronous.

bool fileExists;
try
{
    string fileName = "pictures\\mypicture.png";
    StorageFile file = root.GetFileAsync(fileName).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
    fileExists = file != null;
}
catch (Exception e)
{
    fileExists = false;
}

Benchmarking these methods

As you can see, there is quite a lot of methods, that developer can use, but the problem is some of them are way slower than the other. To find the answer which method is the best one in which API, I've created and ran this simple benchmark:

Each method was tested on each supported platform and on a real devices, in this case on Nokia Lumia 925 running Windows Phone 8.1 and on Surface RT 32GB running Windows 8.1 RT Update 1.
Each method was tested with 500 files stored in LocalFolder in three scenarios - 0/500 files exists, 250/500 files exists and 500/500 files exists.
Each test app was built in Release mode and tested without attached debugger.

And here comes the test results:

Each block represents test for one platform, numbers in colored boxes show time in milliseconds to complete the test for selected method and selected File exists ratio. The rightmost column shows the average time needed to check if one specific file exists or not.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Summary of benchmark test results

Windows Phone 8 - in short: the original Silverlight APIs are MUCH faster than the new Windows.Storage methods. For just checking, if selected file exists or not, it's best to use the File.Exists method. The speed of all other methods is quite similar, no matter if the file exists or not, but if the Windows.Runtime methods are executed synchronously, the performance gets much better. It looks like the the main bottleneck here is the generated overhead for async/await calls. Without these calls the WinRT methods are almost as fast as the Silverlight methods.

Windows Phone 8.1 Silverlight - very similar results to Windows Phone 8 - the Silverlight APIs are much faster than Windows.Storage methods. Again the File.Exists method is the fastest one. But what is interesting here is the result for StorageFolder.GetFilesAsync method. It was quite fast on WP8, but it is terribly slow on WP8.1 Silverlight. One can only guess why is that happening? Again the Windows.Storage methods are much faster if called synchronously, the results are really similar to Windows Phone 8 benchmark.

Windows Phone 8.1 XAML - In this new platform we cannot used Silverlight APIs, but luckily to us, the Windows.Storage methods are here much faster than on original Silvelirght-based WP platforms. The StorageFolder.GetFileAsync method is here almost the fastest one for general use, requiring on average only 1.2 ms to test single file. Again the StorageFolder.GetFilesAsync is really slow and definitely should not be used for file exists checking. The synchronous Windows.Storage methods are here only a bit faster.

Windows 8 - here is the situation almost identical to Windows Phone 8.1 XAML, the fastest method here is the StorageFolder.GetFileAsync method. The synchronous methods are more than 3 times faster than the asynchronous alternatives. If the continuation is not required to run on the original synchronization context, I'd recommend to use the sync methods here.

Windows 8.1 - finally on Windows 8.1 the fastest method is the new StorageFolder.TryGetItemAsync method, but only by a slim margin when comparing to other methods. The main benefit here is definitely the simple code required without any Exception catching if the file does not exists. The results for sync methods are similar to Windows 8 platform, if original synchronization context is not required, the sync methods are a better choice.

 

In short, for checking if target file exists, on WP8 and WP8.1 Silverlight the fastest method is File.Exists, on Windows 8 and WP8.1 XAML you should use StorageFolder.GetFileAsync, and on Windows 8.1 use the new method StorageFolder.TryGetItemAsync. Do NOT use the iteration of StorageFiles returned from StorageFolder.GetFilesAsync on any platform, it is terribly slow. Also if you don't need co continue on the original thread, you can use the synchronous alternatives on WP8.1 XAML, Windows 8 and Windows 8.1 platforms.

That's it for know, in the next parts of the File IO Best Practices mini series I'll show some other tricks, how to effectively access, read and write files on all platforms.

The benchmark app can be downloaded here on GitHub.

And as usual, if you find any errors or have any questions, just contact me on Twitter @martinsuchan, thanks!

  • Pingback: File IO Best Practices in Windows and Phone apps - Part 1. Available APIs and File.Exists checking()

  • pythonflash

    This is an excellent article, thank you. I just resubmitted my beta WP8 app, after replacing GetFileAsync with the GetFileFromPathAsync.
    p.s. what does + exc mean, in your above chart ?

    • Fabio Julián Bernal

      I’m pretty sure that (+ exc) means with exception, so you have to implemente a try catch block to prevent a FileNotFoundException

  • Fabio Julián Bernal

    What a great post, you save me a lot of time testing. I’m just starting to code for Universal Apps.
    Thanks a lot!

  • MsK`

    Another solution that worked for me for winstore apps : just use the good old (almost-)POSIX API ! stat() and _wstat() return -1 (well, 0xFFFFFFFF) if the file doesn’t exist.

  • sanny

    How to access windows phone 8.1 internal memory

  • Pingback: Friday links 210 – A Programmer with Microsoft tools()