Dans le livre Programming C #, il contient un exemple de code sur SynchronizationContext
:
SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(@"c:\temp\log.txt");
originalContext.Post(delegate {
myTextBox.Text = text;
}, null);
});
Je suis un débutant dans les discussions, alors veuillez répondre en détail. Premièrement, je ne sais pas ce que signifie le contexte, qu'est-ce que le programme enregistre dans le originalContext
? Et lorsque la Post
méthode est déclenchée, que fera le thread d'interface utilisateur?
Si je demande des choses idiotes, veuillez me corriger, merci!
EDIT: Par exemple, que se passe-t-il si j'écris simplement myTextBox.Text = text;
dans la méthode, quelle est la différence?
c#
.net
multithreading
nuageuxFan
la source
la source
async
/await
s'appuie sur enSynchronizationContext
dessous.Réponses:
En termes simples,
SynchronizationContext
représente un emplacement «où» le code pourrait être exécuté. Les délégués qui sont passés à sa méthodeSend
ou seront alors appelés à cet emplacement. ( est la version non bloquante / asynchrone de .)Post
Post
Send
Chaque thread peut être
SynchronizationContext
associé à une instance. Le thread en cours d'exécution peut être associé à un contexte de synchronisation en appelant la méthode statiqueSynchronizationContext.SetSynchronizationContext
, et le contexte actuel du thread en cours d'exécution peut être interrogé via laSynchronizationContext.Current
propriété .Malgré ce que je viens d'écrire (chaque thread ayant un contexte de synchronisation associé), a
SynchronizationContext
ne représente pas nécessairement un thread spécifique ; il peut également transmettre l'invocation des délégués qui lui sont passés à l' un de plusieurs threads (par exemple à unThreadPool
thread de travail), ou (au moins en théorie) à un cœur de processeur spécifique , ou même à un autre hôte du réseau . L'endroit où vos délégués finissent par courir dépend du type de serveurSynchronizationContext
utilisé.Windows Forms installera un
WindowsFormsSynchronizationContext
sur le thread sur lequel le premier formulaire est créé. (Ce thread est communément appelé "le thread d'interface utilisateur".) Ce type de contexte de synchronisation appelle les délégués qui lui sont passés exactement sur ce thread. Ceci est très utile car Windows Forms, comme de nombreux autres frameworks d'interface utilisateur, permet uniquement la manipulation des contrôles sur le même thread sur lequel ils ont été créés.Le code que vous avez transmis
ThreadPool.QueueUserWorkItem
sera exécuté sur un thread de travail de pool de threads. Autrement dit, il ne s'exécutera pas sur le thread sur lequel votre amyTextBox
été créé, donc Windows Forms lèvera tôt ou tard (en particulier dans les versions Release) une exception, vous indiquant que vous ne pouvez pas accéder àmyTextBox
partir d'un autre thread.C'est pourquoi vous devez en quelque sorte «revenir» du thread de travail au «thread d'interface utilisateur» (où a
myTextBox
été créé) avant cette affectation particulière. Cela se fait comme suit:Pendant que vous êtes toujours sur le thread d'interface utilisateur, capturez-y Windows Forms
SynchronizationContext
et stockez une référence à celui-ci dans une variable (originalContext
) pour une utilisation ultérieure. Vous devez interrogerSynchronizationContext.Current
à ce stade; si vous l'avez interrogé dans le code passé àThreadPool.QueueUserWorkItem
, vous pouvez obtenir le contexte de synchronisation associé au thread de travail du pool de threads. Une fois que vous avez stocké une référence au contexte de Windows Forms, vous pouvez l'utiliser n'importe où et à tout moment pour «envoyer» du code au thread d'interface utilisateur.Chaque fois que vous avez besoin de manipuler un élément de l'interface utilisateur (mais que vous n'êtes plus, ou peut-être plus, sur le thread de l'interface utilisateur), accédez au contexte de synchronisation de Windows Forms via
originalContext
et transférez le code qui manipulera l'interface utilisateur àSend
ouPost
.Remarques finales et conseils:
Ce que les contextes de synchronisation ne feront pas pour vous, c'est vous dire quel code doit s'exécuter dans un emplacement / contexte spécifique, et quel code peut simplement être exécuté normalement, sans le passer à un
SynchronizationContext
. Pour décider cela, vous devez connaître les règles et les exigences du cadre sur lequel vous programmez - Windows Forms dans ce cas.Rappelez-vous donc cette règle simple pour Windows Forms: N'accédez PAS aux contrôles ou formulaires à partir d'un thread autre que celui qui les a créés. Si vous devez le faire, utilisez le
SynchronizationContext
mécanisme décrit ci-dessus, ouControl.BeginInvoke
(qui est une manière spécifique à Windows Forms de faire exactement la même chose).Si vous programmons contre .NET 4.5 ou version ultérieure, vous pouvez vous rendre la vie beaucoup plus facile en convertissant votre code explicitement usages
SynchronizationContext
,ThreadPool.QueueUserWorkItem
,control.BeginInvoke
, etc. vers les nouveauxasync
/await
mots - clés et la bibliothèque parallèle de tâches (TPL) , à savoir l'API entourant les classesTask
etTask<TResult>
. Ceux-ci, dans une très large mesure, se chargeront de capturer le contexte de synchronisation du thread d'interface utilisateur, de démarrer une opération asynchrone, puis de revenir sur le thread d'interface utilisateur afin que vous puissiez traiter le résultat de l'opération.la source
Application.Run
, IIRC). C'est un sujet assez avancé et non quelque chose de désinvolte.null
) ou une instance deSynchronizationContext
(ou une sous-classe de celui-ci). Le but de cette citation n'était pas ce que vous obtenez, mais ce que vous n'obtiendrez pas : le contexte de synchronisation du thread d'interface utilisateur.Je voudrais ajouter à d'autres réponses, met
SynchronizationContext.Post
simplement en file d'attente un rappel pour une exécution ultérieure sur le thread cible (normalement pendant le cycle suivant de la boucle de message du thread cible), puis l'exécution se poursuit sur le thread appelant. D'autre part,SynchronizationContext.Send
essaie d'exécuter immédiatement le rappel sur le thread cible, ce qui bloque le thread appelant et peut entraîner un blocage. Dans les deux cas, il existe une possibilité de réentrance de code (saisie d'une méthode de classe sur le même thread d'exécution avant le retour de l'appel précédent à la même méthode).Si vous connaissez le modèle de programmation Win32, une analogie très proche serait
PostMessage
etSendMessage
API, que vous pouvez appeler pour envoyer un message à partir d'un thread différent de celui de la fenêtre cible.Voici une très bonne explication de ce que sont les contextes de synchronisation: Tout dépend du SynchronizationContext .
la source
Il stocke le fournisseur de synchronisation, une classe dérivée de SynchronizationContext. Dans ce cas, ce sera probablement une instance de WindowsFormsSynchronizationContext. Cette classe utilise les méthodes Control.Invoke () et Control.BeginInvoke () pour implémenter les méthodes Send () et Post (). Ou cela peut être DispatcherSynchronizationContext, il utilise Dispatcher.Invoke () et BeginInvoke (). Dans une application Winforms ou WPF, ce fournisseur est automatiquement installé dès que vous créez une fenêtre.
Lorsque vous exécutez du code sur un autre thread, comme le thread de pool de threads utilisé dans l'extrait de code, vous devez faire attention à ne pas utiliser directement des objets qui ne sont pas sûrs pour les threads. Comme tout objet d'interface utilisateur, vous devez mettre à jour la propriété TextBox.Text à partir du thread qui a créé le TextBox. La méthode Post () garantit que la cible déléguée s'exécute sur ce thread.
Attention, cet extrait de code est un peu dangereux, il ne fonctionnera correctement que lorsque vous l'appelez depuis le fil de l'interface utilisateur. SynchronizationContext.Current a des valeurs différentes dans différents threads. Seul le thread d'interface utilisateur a une valeur utilisable. Et c'est la raison pour laquelle le code a dû le copier. Une manière plus lisible et plus sûre de le faire, dans une application Winforms:
Ce qui a l'avantage de fonctionner lorsqu'il est appelé depuis n'importe quel thread. L'avantage d'utiliser SynchronizationContext.Current est qu'il fonctionne toujours, que le code soit utilisé dans Winforms ou WPF, cela compte dans une bibliothèque. Ce n'est certainement pas un bon exemple d'un tel code, vous savez toujours quel type de TextBox vous avez ici afin que vous sachiez toujours s'il faut utiliser Control.BeginInvoke ou Dispatcher.BeginInvoke. En fait, l'utilisation de SynchronizationContext.Current n'est pas si courante.
Le livre essaie de vous apprendre sur le threading, donc utiliser cet exemple défectueux est correct. Dans la vraie vie, dans les quelques cas où vous pourriez envisager d'utiliser SynchronizationContext.Current, vous laisseriez toujours le soin aux mots-clés async / await de C # ou à TaskScheduler.FromCurrentSynchronizationContext () de le faire pour vous. Mais notez qu'ils se comportent toujours mal comme l'extrait de code lorsque vous les utilisez sur le mauvais thread, pour exactement la même raison. Une question très courante ici, le niveau supplémentaire d'abstraction est utile mais il est plus difficile de comprendre pourquoi ils ne fonctionnent pas correctement. Espérons que le livre vous indique également quand ne pas l'utiliser :)
la source
Le but du contexte de synchronisation ici est de s'assurer qu'il
myTextbox.Text = text;
est appelé sur le thread d'interface utilisateur principal.Windows exige que les contrôles GUI ne soient accessibles que par le thread avec lequel ils ont été créés. Si vous essayez d'affecter le texte dans un thread d'arrière-plan sans synchroniser d'abord (par l'un de plusieurs moyens, comme celui-ci ou le modèle Invoke), une exception sera levée.
Cela permet d'enregistrer le contexte de synchronisation avant de créer le thread d'arrière-plan, puis le thread d'arrière-plan utilise la méthode context.Post exécute le code GUI.
Oui, le code que vous avez montré est fondamentalement inutile. Pourquoi créer un thread d'arrière-plan, seulement pour avoir immédiatement besoin de revenir au thread principal de l'interface utilisateur? C'est juste un exemple.
la source
À la source
Par exemple: supposons que vous ayez deux threads, Thread1 et Thread2. Disons que Thread1 fait du travail, puis Thread1 souhaite exécuter du code sur Thread2. Une façon possible de le faire est de demander à Thread2 son objet SynchronizationContext, de le donner à Thread1, puis Thread1 peut appeler SynchronizationContext.Send pour exécuter le code sur Thread2.
la source
SynchronizationContext nous fournit un moyen de mettre à jour une interface utilisateur à partir d'un thread différent (de manière synchrone via la méthode Send ou de manière asynchrone via la méthode Post).
Jetez un œil à l'exemple suivant:
SynchronizationContext.Current renverra le contexte de synchronisation du thread d'interface utilisateur. Comment le sais-je? Au début de chaque formulaire ou application WPF, le contexte sera défini sur le thread d'interface utilisateur. Si vous créez une application WPF et exécutez mon exemple, vous verrez que lorsque vous cliquez sur le bouton, il dort pendant environ 1 seconde, puis il affichera le contenu du fichier. Vous pourriez vous attendre à ce que ce ne soit pas le cas, car l'appelant de la méthode UpdateTextBox (qui est Work1) est une méthode passée à un thread, il devrait donc dormir ce thread et non le thread d'interface utilisateur principal, NOPE! Même si la méthode Work1 est passée à un thread, notez qu'elle accepte également un objet qui est le SyncContext. Si vous le regardez, vous verrez que la méthode UpdateTextBox est exécutée via la méthode syncContext.Post et non la méthode Work1. Jetez un œil à ce qui suit:
Le dernier exemple et celui-ci exécute la même chose. Les deux ne bloquent pas l'interface utilisateur pendant son exécution.
En conclusion, considérez SynchronizationContext comme un thread. Ce n'est pas un thread, il définit un thread (notez que tous les threads n'ont pas un SyncContext). Chaque fois que nous appelons la méthode Post ou Send pour mettre à jour une interface utilisateur, c'est comme mettre à jour l'interface normalement à partir du fil de discussion principal de l'interface utilisateur. Si, pour certaines raisons, vous devez mettre à jour l'interface utilisateur à partir d'un thread différent, assurez-vous que ce thread a le SyncContext du thread d'interface utilisateur principal et appelez simplement la méthode Send ou Post dessus avec la méthode que vous souhaitez exécuter et vous êtes tous ensemble.
J'espère que cela vous aide, mon pote!
la source
SynchronizationContext est essentiellement un fournisseur d'exécution de délégués de rappel principalement responsable de garantir que les délégués sont exécutés dans un contexte d'exécution donné après une partie particulière du code (incorporé dans un objet de tâche de .Net TPL) d'un programme a terminé son exécution.
Du point de vue technique, SC est une classe C # simple qui est orientée pour prendre en charge et fournir sa fonction spécifiquement pour les objets Task Parallel Library.
Chaque application .Net, à l'exception des applications console, a une implémentation particulière de cette classe basée sur le framework sous-jacent spécifique, à savoir: WPF, WindowsForm, Asp Net, Silverlight, etc.
L'importance de cet objet est liée à la synchronisation entre les résultats retournant d'une exécution asyncrone de code et l'exécution de code dépendant qui attend les résultats de ce travail asynchrone.
Et le mot «contexte» signifie contexte d'exécution, c'est-à-dire le contexte d'exécution actuel où ce code en attente sera exécuté, à savoir la synchronisation entre le code asynchrone et son code d'attente se produit dans un contexte d'exécution spécifique, donc cet objet s'appelle SynchronizationContext: il représente le contexte d'exécution qui s'occupera de la synchronisation du code asynchrone et de l'exécution du code en attente .
la source
Cet exemple est tiré des exemples Linqpad de Joseph Albahari, mais il aide vraiment à comprendre ce que fait le contexte de synchronisation.
la source