This post is available in english.
En d’autres mots, il n’est pas supporté !
Et le pire est que l’on est même pas averti que ce n’est pas supporté... Le code compile, mais l'attribut n'a aucun effet ! On peut bien entendu lire l’article “the differences between silverlight on Windows and Windows Phone”, mais bon, il est facile de l’oublier. Peut-être qu’une règle d’analyse statique de code pourrait empêcher cela.
Mais enfin, vous voudrez probablement utiliser ThreadStatic parce que vous en avez besoin. Mais comme ce n’est pas supporté, vous pourriez aller vers Thread.GetNamedDataSlot, vous me direz.
Pas de chance. Ce n’est pas supporté non plus.
Cela nous laisse à l’implémentation de notre propre TLS, à la main.
Mise à jour de Umbrella pour Silverlight sur Windows Phone
Je suis un grand fan d’Umbrella, et la première fois que j’ai eu à utiliser Dictionary<>.TryGetValue et son magique paramètre “out”, dans l'essai de portage de mon application de Controle à Distance, j’ai décidé de porter Umbrella vers Windows Phone 7. Pour enfin utiliser GetValueOrDefault sans le réécrire, encore.
J’ai réussi à faire passer la majorité des tests unitaires, excepté ceux qui émettent du code, utilisent des fonctionnalités du web, utilisent les sérialiseurs xml et binaires, appellent des méthodes privées via la réflection, et ainsi de suite.
Il y a quelques autres parties qui nécessitaient d’être mise à jour, parceque la classe TypeDescriptor n’est pas disponible en WP7, et il faut passer par un try/catch pour vérifier qu’une valeur est convertible d’un type vers un autre. Mais ce n’est pas vraiment gênant, et cela fonctionne comme attendu.
ThreadLocalSource dans Umbrella
Umbrella contient une classe nommée ThreadLocalSource qui encapsule le comportement du TLS, et il est très facile de créer une variable statique de ce type, plutôt que d’utiliser une variable statique ThreadStatic.
Les exemples Quick Start en font ce genre d’utilisation :
1 2 3 4 5 6 7 8 9 10 | 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); |
La Thread principale place la valeur à 1, et l’autre thread essaye de lire la même variable et elle doit être différente. (La valeur par défaut d’un int, qui est 0).
Mise à jour de ThreadLocalSource pour éviter l’utilisation de ThreadStatic
L’implémentation du TLS dans .NET est principalement un dictionnaire de paires de string/object qui est attaché à chaque thread active. Donc pour mimiquer cela, on a simplement besoin de créer une liste de toutes les threads qui ont besoin de stocker quelque chose pour elles-mêmes, et l’encapsuler proprement.
On peut créer une variable comme celle-ci :
1 | private static Tuple<WeakReference, IDictionary<string, T>>[] _tls; |
Cette variable est intentionnellement du type d’un tableau pour tenter d’utiliser la localité spatiale en mémoire. Puisque sur cette plateforme il ne devrait pas y avoir beaucoup de threads, il est tout à fait acceptable de parcourir le tableau pour en trouver une. Cette approche tente d’être sans locks, en utilisant un mécanisme de “retry” pour mettre à jour le tableau. Le type WeakReference est utilisé pour éviter de garder une référence vers les threads une fois qu’elles sont terminées.
Donc, pour faire la mise à jour du tableau, on peut s’y prendre comme suit :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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; } |
L’utilisation d’une approche sans lock comme celle-ci devrait limiter la contention autour de l’utilisation de cette classe de simili-TLS. Il pourrait y avoir, de temps à autres, des manipulations faites plusieurs fois lors de “race conditions” sur la mise à jour de la variable _tls, mais cela est tout à fait acceptable. De plus, les livelocks ne peuvent pas arriver, considérant le type de système préemptif sur lequel WP7 fonctionne.
J’ai l’impression que développer sur cette plateforme va nécessiter plein de petits “hacks” du genre... Ca va être intéressant !