Showing posts with label Multithreading. Show all posts
Showing posts with label Multithreading. Show all posts

Thursday, 8 March 2012

How to monitor garbage collections in a WP7 app

I’m currently working on an app that has the potential to use lots of memory, and I’d like to cache the results as much as I can to speed things up for the user.

I’d also like to get my app certified and available for sale, so must be careful not to eat up all the memory.  One way to do that is with a WeakReference object, but I found that my objects were being garbage collected at the first opportunity, which didn’t really help.

I thought about doing an ephemeral garbage collection by calling GC.Collect(0) on the phone, but that isn’t allowed, and throws an exception.

What I really needed to do was keep my objects alive for a little while after they were used, but I really want to define a little while in terms of the number of garbage collections, rather than running a timer to continually dispose objects.

Unfortunately, I discovered there wasn’t any obvious event raised when a garbage collection is fired, but a little lateral thinking let me realize that there is an event that is called, thought it’s not marked as an event.

It’s actually every object’s finalizer!

So my strategy is to create an object, to which I hold only a weak reference, and when it’s garbage collected I update my statistics, and perform whatever other GC related actions I need to on the UI thread.

To save anyone else the pain, I include the code below.  To fire it up, just reference GCWatcher.GCINFO somewhere in your application, and the notifications begin.

public class GCWatcher
{
/// <summary>
/// static constructor collects the total memory on the device
/// </summary>
static GCWatcher()
{
TotalMemory = (long)DeviceExtendedProperties.GetValue("DeviceTotalMemory");
}

/// <summary>
/// Store a weak reference to our current sacrificial garbage collector object
/// </summary>
static WeakReference _activeGC;

/// <summary>
/// keep a count of the total number of garbage collections seen
/// </summary>
static int gcCount = 0;

/// <summary>
/// store the total memory found on the device
/// </summary>
public static readonly long TotalMemory;

/// <summary>
/// This placeholder should be modified to raise whatever notifications you need in your application
/// It will always be called on the main thread.
/// </summary>
static void Notify()
{
// placeholder for our notification call
}

/// <summary>
/// Property to get the current Garbage Collection info object. If one does not exist it will be created.
/// </summary>
static public GCWatcher GCINFO
{
get
{
WeakReference currentwr = Interlocked.CompareExchange(ref _activeGC, null, null);
GCWatcher current = currentwr == null ? null : currentwr.Target as GCWatcher;
if (current == null)
{
current = new GCWatcher();
Interlocked.Exchange(ref _activeGC, new WeakReference(current));
}
return current;
}
}

/// <summary>
/// Instance property to return the memory usage at the most recent garbage collection
/// </summary>
public long ApplicationCurrentMemoryUsage
{
get;
set;
}

/// <summary>
/// Instance property to return the peak memory usage at the most recent garbage collection
/// </summary>
public long ApplicationPeakMemoryUsage
{
get;
set;
}

/// <summary>
/// protected constructor, to create a new GCWatcher object, and initialize the properties.
/// This is always called on the UI thread.
/// </summary>
internal GCWatcher()
{
// when we create a GC Watcher, we collect the statistics
ApplicationCurrentMemoryUsage = (long) DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage");
ApplicationPeakMemoryUsage = (long) DeviceExtendedProperties.GetValue("ApplicationPeakMemoryUsage");

}

/// <summary>
/// implement a finalizer, that keeps track of our garbage collections via the weak referened object
/// </summary>

~GCWatcher()
{
Interlocked.Exchange(ref _activeGC, null); // zap the value, as we're destroyed
Interlocked.Increment(ref gcCount);

Deployment.Current.Dispatcher.BeginInvoke(
// create a new GCWatcher on the main thread.
() =>
{
WeakReference tmp = new WeakReference(new GCWatcher());
Interlocked.Exchange(ref _activeGC, tmp);
GCWatcher.Notify();
});
}

/// <summary>
/// Thread safe property to return the total counts of garbage collections
/// </summary>
int Count
{
get
{
return Interlocked.CompareExchange(ref gcCount, 0, 0);
}
}

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.