Il semble que je ne trouve pas comment obtenir le SynchronizationContext
d’un Thread
donné:
Thread uiThread = UIConfiguration.UIThread; SynchronizationContext context = uiThread.Huh?;
Pourquoi aurais-je besoin de ça?
Parce que je dois publier sur UIThread à partir de différents endroits sur toute l’application front-end. J’ai donc défini une propriété statique dans une classe appelée UIConfiguration
. J’ai défini cette propriété dans la méthode Program.Main
:
UIConfiguration.UIThread = Thread.CurrentThread;
À ce moment-là, je peux être sûr d’avoir le bon thread, mais je ne peux pas définir de propriété statique telle que
UIConfiguration.SynchronizationContext = SynchronizationContext.Current
car l’implémentation WinForms de cette classe n’a pas encore été installée. Puisque chaque thread a son propre SynchronizationContext, il doit être possible de le récupérer à partir d’un object Thread
donné, ou suis-je complètement dans l’erreur?
Ce n’est pas possible. Le problème est qu’un SynchronizationContext
et un Thread
sont en réalité deux concepts complètement distincts.
S’il est vrai que Windows Forms et WPF configurent tous deux un SynchronizationContext
pour le thread principal, la plupart des autres threads ne le font pas. Par exemple, aucun des threads dans le ThreadPool ne contient son propre SynchronizationContext (à moins que, bien sûr, vous n’installiez le vôtre).
Il est également possible qu’un SynchronizationContext
soit complètement indépendant des threads et du threading . Un contexte de synchronisation peut facilement être configuré pour se synchroniser avec un service externe, ou avec un pool de threads complet, etc.
Dans votre cas, je vous recommande de définir votre UIConfiguration.SynchronizationContext
dans l’événement Loaded du formulaire initial. Le contexte est garanti pour être démarré à ce point et sera inutilisable jusqu’à ce que le message pompe a été démarré dans tous les cas.
Je sais que c’est une vieille question et je m’excuse pour la nécro, mais je viens de trouver une solution à ce problème qui, selon moi, pourrait être utile pour ceux d’entre nous qui ont recherché cela (et cela ne nécessite pas d’instance de contrôle).
En gros, vous pouvez créer une instance de WindowsFormsSynchronizationContext et définir le contexte manuellement dans votre fonction Main
, comme suit:
_UISyncContext = new WindowsFormsSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(_UISyncContext);
Je l’ai fait dans mon application, et cela fonctionne parfaitement sans problèmes. Cependant, je devrais souligner que mon Main
est marqué avec STAThread, donc je ne suis pas sûr si cela fonctionnera toujours (ou si cela est même nécessaire) si votre Main
est marqué avec MTAThread à la place.
EDIT: J’ai oublié de le mentionner, mais _UISyncContext
est déjà défini au niveau du module dans la classe Program
de mon application.
J’ai trouvé les passages suivants du livre d’Alex Davies “Async in C # 5.0. O’Reilly Publ., 2012”, p.48-49:
” SynchronizationContext est une classe fournie par le .NET Framework , qui permet d’exécuter du code dans un type de thread particulier .
Il existe différents contextes de synchronisation utilisés par .NET, les plus importants étant les contextes de thread d’interface utilisateur utilisés par WinForms et WPF. ”
“Les instances de SynchronizationContext
lui-même ne font rien de très utile, donc toutes les instances réelles de celui-ci tendent à être des sous-classes.
Il a également des membres statiques qui vous permettent de lire et de contrôler le SynchronizationContext
actuel.
Le SynchronizationContext
actuel est une propriété du thread actuel.
L’idée est que, quel que soit le moment où vous exécutez un thread spécial, vous devriez pouvoir obtenir le SynchronizationContext
actuel et le stocker. Plus tard, vous pourrez l’utiliser pour exécuter du code sur le thread spécial sur lequel vous avez commencé. Tout cela devrait être possible sans qu’il soit nécessaire de savoir sur quel thread vous avez démarré, tant que vous pouvez utiliser SynchronizationContext, vous pouvez y revenir .
La méthode importante de SynchronizationContext est Post
, qui peut faire en sorte qu’un délégué s’exécute dans le bon contexte ”
.
” Certains SynchronizationContexts encapsulent un seul thread, comme le thread UI .
Certains encapsulent un type de thread particulier – par exemple, le pool de threads – mais peuvent choisir l’un de ces threads sur lequel envoyer le délégué . Certaines ne changent pas réellement le thread sur lequel le code est exécuté, mais ne sont utilisées que pour la surveillance, comme le contexte de synchronisation ASP.NET ”
Je ne crois pas que chaque thread a son propre SynchronizationContext
– il a juste un SynchronizationContext
thread.
Pourquoi ne définissez-vous pas simplement UIConfiguration.UIThread
dans l’événement Loaded
de votre formulaire ou quelque chose de similaire?
Méthodes d’extension complètes et fonctionnelles pour obtenir le SynchronizationContext
partir d’un Thread
ou ExecutionContext
(ou null
si aucun n’est présent), ou un DispatcherSynchronizationContext
partir d’un Dispatcher
. Testé sur .NET 4.6.2 .
using Ectx = ExecutionContext; using Sctx = SynchronizationContext; using Dctx = DispatcherSynchronizationContext; public static class _ext { // DispatcherSynchronizationContext from Dispatcher public static Dctx GetSyncCtx(this Dispatcher d) => d?.Thread.GetSyncCtx() as Dctx; // SynchronizationContext from Thread public static Sctx GetSyncCtx(this Thread th) => th?.ExecutionContext?.GetSyncCtx(); // SynchronizationContext from ExecutionContext public static Sctx GetSyncCtx(this Ectx x) => __get(x); /* ... continued below ... */ }
Toutes les fonctions ci-dessus finissent par appeler le code __get
indiqué ci-dessous, ce qui justifie certaines explications.
Notez que __get
est un champ statique, pré-initialisé avec un bloc lambda à éliminer. Cela nous permet d’intercepter proprement le premier appelant uniquement , afin d’exécuter l’initialisation unique, ce qui prépare un délégué de remplacement minuscule et permanent qui est beaucoup plus rapide et qui ne réfléchit pas.
Le dernier acte de l’intrépide effort d’initialisation consiste à permuter le remplacement en ‘__get’, ce qui signifie simultanément et tragiquement que le code se défait, ne laissant aucune trace, et que tous les appelants suivants se rendent directement dans la DynamicMethod
proprement dite, sans même un soupçon de logique de contournement.
static Func __get = arg => { // Hijack the first caller to do initialization... var fi = typeof(Ectx).GetField( "_syncContext", // private field in 'ExecutionContext' BindingFlags.NonPublic|BindingFlags.Instance); var dm = new DynamicMethod( "foo", // (any name) typeof(Sctx), // getter return type new[] { typeof(Ectx) }, // type of getter's single arg typeof(Ectx), // "owner" type true); // allow private field access var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fi); il.Emit(OpCodes.Ret); // ...now replace ourself... __get = (Func)dm.CreateDelegate(typeof(Func)); // oh yeah, don't forget to handle the first caller's request return __get(arg); // ...never to come back here again. SAD! };
La partie mignonne est la fin même où, afin de récupérer la valeur pour le premier appelant préempté, la fonction s’appelle apparemment avec son propre argument, mais évite de se répéter en se remplaçant immédiatement avant.
Il n’y a aucune raison particulière de démontrer cette technique inhabituelle au problème particulier de SynchronizationContext
en discussion sur cette page. _syncContext
champ _syncContext
d’un _syncContext
d’ ExecutionContext
peut être facilement et sortingvialement abordé avec une reflection traditionnelle (plus un glaçage par méthode d’extension). Mais j’ai pensé partager cette approche que j’ai personnellement utilisée depuis longtemps, car elle s’adapte facilement et s’applique aussi largement à ce type de situation.
Cela convient particulièrement lorsque des performances extrêmes sont nécessaires pour accéder au domaine non public. Je pense que je l’avais initialement utilisé dans un compteur de fréquence basé sur QPC, dans lequel le champ était lu dans une boucle serrée qui se répétait toutes les 20 ou 25 nanosecondes, ce qui ne serait pas vraiment possible avec une reflection conventionnelle.
Ceci conclut la réponse principale, mais ci-dessous, j’ai inclus quelques points intéressants, moins pertinents pour l’enquête du questionneur, plus que pour la technique que nous venons de démontrer.
Appels à l’exécution
Par souci de clarté, j’ai séparé les étapes “installation swap” et “première utilisation” en deux lignes distinctes dans le code présenté ci-dessus, par opposition à ce que j’ai dans mon propre code (la version suivante évite également une extraction de mémoire principale par rapport à la précédente). , impliquant potentiellement la sécurité des threads, voir la discussion détaillée ci-dessous):
return (__get = (Func)dm.CreateDel...(...))(arg);
En d’autres termes, tous les appelants, y compris le premier , récupèrent la valeur exactement de la même manière, et aucun code de reflection n’est jamais utilisé pour le faire. Il écrit seulement le getter de remplacement. Avec la permission de il-visualizer , nous pouvons voir le corps de cette DynamicMethod
dans le débogueur au moment de l’exécution:
L’application ClickOnce ignore la demande de mise à jour (ou le lancement échoue si l’option ignorer est sélectionnée)