Obtenir le SynchronizationContext à partir d’un thread donné

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:

ldarg.0 <br /> ldfld SynchronizationContext _syncContext / ExecutionContext <br /> ret”> </p>
<p>  <strong>Sécurité du filetage sans blocage</strong> </p>
<p>  Il convient de noter que l’échange dans le corps de la fonction est une opération entièrement sécurisée pour les threads, étant donné le modèle de mémoire .NET et la philosophie sans locking.  Ce dernier privilégie les garanties de progrès aux frais éventuels des travaux en double ou redondants.  Les courses à plusieurs voies à initialiser sont correctement autorisées sur une base théorique parfaitement solide: </p>
<ul>
<li>  le point d’entrée de course (code d’initialisation) est globalement préconfiguré et protégé (par le chargeur .NET), de sorte que plusieurs concurrents (le cas échéant) entrent dans le même initialiseur, qui ne peut jamais être considéré comme <code>null</code> . </li>
<li>  les produits de course multiples (le getter) sont toujours logiquement identiques, donc peu importe le coureur (ou l’appelant non coureur) à suivre, ou même si l’un des coureurs finit par utiliser celui qu’ils ont eux-mêmes produit; </li>
<li>  chaque échange d’installation est un magasin unique de taille <code>IntPtr</code> , qui est garanti pour être atomique pour tout bitness de plate-forme respectif; </li>
<li>  enfin, et techniquement <em>absolument indispensables à la perfection de la correction formelle</em> , les produits de travail des “perdants” sont récupérés par <code>GC</code> et ne fuient donc pas.  Dans ce type de course , les perdants sont tous les coureurs sauf le <strong><em>dernier</em></strong> (car les efforts de tous les autres sont écrasés et sommairement écrasés avec un résultat identique). </li>
</ul>
<p>  Bien que je pense que tous ces éléments concourent à protéger complètement le code tel qu’il est écrit dans toutes les circonstances, si vous êtes toujours méfiant ou si vous méfiez de la conclusion générale, vous pouvez toujours append une couche supplémentaire de protection anti-balles: </p>
<pre> <code>var tmp = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>)); Thread.MemoryBarrier(); __get = tmp; return tmp(arg);</code> </pre>
<p>  C’est juste une version paranodale.  Comme avec l’ancien modèle one-liner condensé, le modèle de mémoire .NET garantit qu’il existe exactement un <strong>magasin</strong> – et aucune <strong>extraction –</strong> vers l’emplacement de ‘__get’.  (L’exemple complet en haut effectue une extraction de mémoire principale supplémentaire, mais est toujours valide grâce au deuxième point.) Comme je l’ai mentionné, rien de tout cela ne devrait être nécessaire pour la correction, mais il pourrait théoriquement donner une minuscule Bonus de performance: en mettant fin de manière concluante à la course <em>plus tôt</em> , la couleur agressive pourrait, dans des cas extrêmement rares, empêcher un autre appelant sur une ligne de cache sale de courir inutilement (mais à nouveau de manière inoffensive). </p>
<p>  <strong>Double-parole</strong> </p>
<p>  Les appels dans la méthode finale hyper-rapide sont toujours traités via les méthodes d’extension statique présentées précédemment.  En effet, nous devons également représenter les points d’entrée qui existent réellement au moment de la compilation pour que le compilateur puisse se lier et propager les métadonnées.  Le double thunk est un petit prix à payer pour la commodité écrasante des métadonnées fortement typées et intellisense dans l’EDI pour un code personnalisé qui ne peut en réalité être résolu avant l’exécution.  Pourtant, il tourne au moins aussi vite que du code compilé de manière statique, <em>beaucoup</em> plus rapidement que de faire une série de reflections sur chaque appel, pour que nous puissions tirer le meilleur parti des deux mondes! </p>
</div>
</li><!-- #comment-## -->

 	</div>
		
        </div>
        
<ul>
<ul><li><a href=L’application ClickOnce ignore la demande de mise à jour (ou le lancement échoue si l’option ignorer est sélectionnée)

  • Utilisation de l’opérateur conditionnel (? :) pour la sélection de la méthode en C # (3.0)?
  • Une classe wrapper pour un COM interop IStream existe-t-elle déjà?
  • code c # pour tout cocher dans la grid de données wpf
  • Application .Net Console dans la barre d’état système
  • Activation de la queue avec access simultané
  • Modèles d’interface d’extension
  • Chaîne de dissortingbution explicite C # à énumérer
  • Appelez les API Web en C # à l’aide de .NET Framework 3.5
  • Linq sélectionner certaines propriétés dans un autre object?