[WP7Dev] Beware of the [ThreadStatic] attribute on Silverlight for Windows Phone 7
Cet article est disponible en francais.
In other words, it is not supported !
And the worst in all this is that you don’t even get warned that it’s not supported... The code compiles, but the attribute has no effect at all ! Granted that you can read the msdn article about the differences between silverlight on Windows and Windows Phone, but well, you may still miss it. Maybe a custom code analysis rule could prevent this.
Still, you want to use ThreadStatic because you probably need it, somehow. But since it is not supported, you could try the Thread.GetNamedDataSlot, mind you.
Well, too bad. It’s not supported either.
That leaves us implementing or own TLS implementation, by hand...
Updating Umbrella for Silverlight on Windows Phone
I’m a big fan of Umbrella, and the first time I had to use Dictionary<>.TryGetValue and its magically aweful out parameter in my attempt to rewrite my Remote Control app for Windows Phone 7, I decided to port Umbrella to it. So I could use GetValueOrDefault without rewriting it, again.
I managed to get almost all the desktop unit tests to pass, except for those who emit code, use web features, use xml and binary serializers, call private methods using reflection, and so on.
There are a few parts where the code needed to be updated, because TypeDescriptor class is not available on WP7, you have to crash and burn to see if a value is convertible from one type to the other. But that’s not too bad, it works as expected.
Umbrella’s ThreadLocalSource
Umbrella has this nice ThreadLocalSource class that wraps the TLS behavior, and you can easily create a static variable of that type instead of the ThreadStatic static variable.
The Umbrella quick start samples make that kind of use for it :
ISource<int> threadLocal = new ThreadLocalSource<int>(1); int valueOnOtherThread = 0; Thread thread = new Thread(() => valueOnOtherThread = threadLocal.Value); thread.Start(); thread.Join(); Assert.Equal(1, threadLocal.Value); Assert.Equal(0, valueOnOtherThread);
The main thread set the value to 1, and the other thread tries to get the same value from the other thread and it should be different (the default value of an int, which is 0).
Updating the ThreadLocalSource to avoid the use of ThreadStatic
The TLS in .NET is basically a dictionary of string/object pairs that is attached to each running threads. So, to mimic this, we just need to make a list of all threads that want to store something for themselves and wrap it nicely.
We can create a variable of this type :
private static Tuple<WeakReference, IDictionary<string, T>>[] _tls;
That variable is intentionally an array to try to make use of memory spacial locality, and since on that platform we won’t get a lot of threads, this should be fine when we got through the array to find one. This approach is trying to be lockless, by using a retry mechanism to update the array. The WeakReference is used to avoid keeping a reference to the thread after it has been terminated.
So, to update the array, we can do as follows :
private static IDictionary<string, T> GetValuesForThread(Thread thread) { // Find the TLS for the specified thread var query = from entry in _tls // Only get threads that are still alive let t = entry.T.Target as Thread // Get the requested thread where t != null && t == thread select entry.U; var localStorage = query.FirstOrDefault(); if (localStorage == null) { bool success = false; // The storage for the new Thread localStorage = new Dictionary<string, T>(); while(!success) { // store the original array so we can check later if there has not // been anyone that has updated the array at the same time we did var originalTls = _tls; var newTls = new List<Tuple<WeakReference, IDictionary<string, T>>>(); // Add the slots for which threads references are still alive newTls.AddRange(_tls.Where(t => t.T.IsAlive)); var newSlot = new Tuple<WeakReference, IDictionary<string, T>>() { T = new WeakReference(thread), U = localStorage }; newTls.Add(newSlot); // If no other thread has changed the array, replace it. success = Interlocked.CompareExchange(ref _tls, newTls.ToArray(), originalTls) != _tls; } } return localStorage; }
Instead of the array, another dictionary could be created but I’m not sure of the actual performance improvement that would provide, particularly for very small arrays.
Using a lockless approach like this one will most likely limit the contention around the use of that TLS-like class. There may be, from time to time, computations that are performed multiple times in case of race conditions on the update of the _tls array, but that is completely acceptable. Additionally, livelocks are also out of the picture on that kind of preemptive systems.
I think developing on that platform is going to be fully of little workarounds like this one... This is going to be fun !