Showing posts with label IsolatedStorage. Show all posts
Showing posts with label IsolatedStorage. Show all posts

Wednesday, 7 March 2012

Thread Safety on WPF, Silverlight and WP7

About a week ago I posted about IsolatedStorage and Thread Safety and what exactly the statement Any instance members are not guaranteed to be thread safe means in the documentation of the class libraries.

A lot of discussion making superstitious claims about serializing access to instance members ensued, however all of those led to illogical conclusions, implying that it was never possible to write a program that used multiple threads.

Eventually, I contacted members of the Base Class Library team who responded with the following definitive statements:

In general, the overall Framework guidance should apply to IsolatedStorage as well.

- Static members should be thread safe.

- Instance members cannot be expected to be thread safe unless otherwise specified.

- Instance members on different instances should be safe to use concurrently.

In addition, specifically related to IsolatedStorage, I got this definitive statement:

If you work on distinct instances of IsolatedStorage / ..File / ..Stream on different threads you will not run into any concurrency issues.

Different instances of those types have private copies of all internal data; and in cases where we need to update global state (e.g. isolated storage quota) we do all the necessary locking for you.

Note, however, that if you open two distinct Stream instances on the same file – whether through the iso storage or directly thought FileStream, we do not make any guarantees about the order in which these instances will access the underlying physical file. When using two distinct stream instances on the same file concurrently from different threads, you may end up with interleaved, mixed or invalid data in the file. However, the stream objects themselves will remain consistent and valid.

This is different from using one single Stream object from different threads: That is not supported and can break the stream object itself, not only resulting in data corruption, but also in weird runtime behaviour.

So there you have it.  Put your mutexes and locks down and step away from the code face and think!

If you are seeing what looks like a threading problem, and you found a mutex or lock solved it, it probably has, but not for the reason you thought.  There is probably some other multithreading logic error you had not foreseen, and your lock will either kill performance, or just make the bug even harder to find.

Friday, 24 February 2012

IsolatedStorage and Thread Safety

In the documentation of the IsolatedStorageFile class, Microsoft say this:

Any instance members are not guaranteed to be thread safe.

Normally, members not being guaranteed to be thread safe means that when you have an object, the object cannot be shared across threads and have members called from more than one thread.

I suspect either that is not what is intended, or there’s a serious bug.

If you’ve followed my two previous posts today, you’ll notice that I’ve been trying to read from IsolatedStorage on a ThreadPool thread, and was debating whether I needed to create a separate IsolatedStorageFile for each thread.

Interepreting the statement in the documentation strictly implies that I should call GetUserStoreForApplication to get a new instance whenever I want to examine isolated storage.

Now, imagine my surprise when I fire up more than one thread pool thread, and get an exception when calling GetFileNames with a set of non-overlapping folders as follows:

System.IO.IsolatedStorage.IsolatedStorageException

An error occurred while accessing IsolatedStorage.at System.IO.IsolatedStorage.IsolatedStorageFile.EnsureStoreIsValid()
at System.IO.IsolatedStorage.IsolatedStorageFile.GetFileNames(String searchPattern)
at App.Model.DaySummary.BackgroundProcess()
at Microsoft.Phone.Reactive.Observable.<>c__DisplayClassa0.<>c__DisplayClassa2.<ToAsync>b__9f()
at Microsoft.Phone.Reactive.ThreadPoolScheduler.<>c__DisplayClass1.<Schedule>b__0(Object _)
at System.Threading.ThreadPool.WorkItem.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadPool.WorkItem.doWork(Object o)
at System.Threading.Timer.ring()

So it now looks like I need to protect this with some kind of lock to prevent more than one object accessing any aspect of IsolatedStorage at a time.  Adding a lock around the creation of the IsolatedStorage object certainly prevents the error.

Addendum

Well, it looks like I had a bug.  When I rewrote my wrapper class for getting the IsolatedStorageFile via GetUserStoreForApplication, I inadvertently forgot to remove the static keyword from the variable holding it.  As a result, I was using the same object across threads.

In that situation, as you would expect, the object failed, as it is not guaranteed thread safe.  Removing the static keyword is all it took to fix the problem, so for the moment it looks like separate instances can be used at will on a single thread without regard to what else is accessing isolated storage (from a thread safety point anyway).

update March 1 2012

Quite a lot of discussion has happened on the AppHub forums on this since it was originally written, concerning thread safety of the entire platform.

If you’re interested, the whole discussion is here.

IsolatedStorage performance tests on WP7

The documentation on IsolatedStorage seems to be incomplete on the subject of when you should open and close the ApplicationStore, particularly since the only the public static members are thread safe.

It would appear that the best performance might be to open the IsolatedStorage using IsolatedStorageFile.GetUserStoreForApplication() once for the application, and share the instance, but what operations are allowed across multiple threads if you do?  Well, that isn’t clear.

So, the safest thing to do is to open one every time you need it, and dispose of it when finished.

I’ve already verified that each time you call GetUserStoreForApplication, a different object is returned, and built a harness that does this:

isf = IsolatedStorageFile.GetUserStoreForApplication();

int v = System.Environment.TickCount;

for (int i = 0;i<1000;i++)
{

CheckFolderBare("Shared");
}

int v2 = System.Environment.TickCount - v;
v = System.Environment.TickCount;

for (int i= 0;i<1000;i++)
{
CheckFolderSlow("Shared");
}
int v3 = System.Environment.TickCount - v;

MessageBox.Show(string.Format("Fast: {0}ms\nSlow {1}ms", v2, v3));


My Implementations of CheckFolderBare and CheckFolderSlow are this:


private bool CheckFolderBare(string s)
{
return isf.DirectoryExists(s);
}

private bool CheckFolderSlow(string s)
{
using (var isf2 = IsolatedStorageFile.GetUserStoreForApplication())
{
return isf2.DirectoryExists(s);
}
}

 

Not surprisingly, my initial suspicions were correct.  It isn’t particularly cheap to create and dispose of the file (taking about 1.3ms).  Timings were:

Fast: 2787ms

Slow: 4086ms

 

How about enumerating the folders?  To do this, I create a folder in IsolatedStorage named Logs, and created in that folder 7 additional folders, and then ran these functions 1000 times:

private string[] FoldersSlow()
{
using (var isf2 = IsolatedStorageFile.GetUserStoreForApplication())
{
return isf2.GetDirectoryNames("Logs/*.*");
}
}

private string[] FoldersFast()
{
return isf.GetDirectoryNames("Logs/*.*");
}



Results differed by about 2.5ms:


Fast: 7619ms
Slow: 9100ms


So, to make things more interesting, I decided to create and delete a folder on each operation, so on odd calls, I remove the folder Logs/Bogus and on even calls I created it.


Surprisingly, I ended up with times differing by 1.3ms, and in some cases, not showing any difference at all.


Fast: 16406ms
Slow: 17542ms


Verdict?


On balance, I’d say there is no real performance penalty by opening and closing the IsolatedStorage in a realistic usage scenario.