public static void Raise<T>(this object sender, ref EventHandler<T> handler, object eventLock, T e) where T : EventArgs
{
EventHandler<T> copy;
lock (eventLock)
{
copy = handler;
}
if (copy != null)
{
copy(sender, e);
}
}
7 Réponses :
lock (@lock) { handlerCopy = handler; } Assignment of basic types like a references are atomic, so there is no point of using a lock here.
Alors, pourquoi l'exemple de Jon Skeet utilise-t-il une serrure? Il mentionne également que le Ajouter
/
Supprimer code> Les accesseurs des événements doivent utiliser le même verrou.
L'atomicité ne suffit pas toujours - l'utilisation d'un verrou signifie que vous obtenez la valeur la plus récente, plutôt qu'un potentiellement caché.
@Jon: Étant donné que la référence du champ délégué est modifiée par le + = code> et
- = code> (comme vous l'avez expliqué ci-dessous), est l'utilisation de la serrure de ma méthode d'extension toujours Valid et va-t-il toujours atteindre ce qu'il est censé? Passer le champ délégué sous forme de paramètre à la méthode de l'extension passera simplement la référence, de sorte que si cette référence est mutée par un autre thread par
+ = code> ou
- = code>, la méthode de l'extension Je ne saurai pas à ce sujet, n'est-ce pas?
Le but de la serrure est de maintenir la sécurité du thread lorsque vous remplacez les fil-ups par défaut. Toutes mes excuses si certaines d'entre elles expliquent des choses que vous étiez déjà en mesure de déduire de l'article de Jon; Je veux juste m'assurer que je suis complètement clair sur tout.
Si vous déclarez vos événements comme celui-ci: p>
public static void RaiseEvent(ref EventHandler handler, object sync, object sender, EventArgs e) { EventHandler handlerCopy; lock (sync) { handlerCopy = handler; } if (handlerCopy != null) { handlerCopy(sender, e); } }
Merci d'avoir répondu. Je ne comprends toujours pas bien pourquoi il est nécessaire de synchroniser l'accès en lecture / écriture au délégué du support, cependant. Vous dites que clickhandler = _click code> est i> atomique, mais qu'en est-il de
_click + = valeur code> _cliquez - valeur code>? N'est-ce pas aussi atomique? De plus, Mark Simpson a mentionné dans un commentaire sur ma question que l'acte d'appeler la méthode d'extension avec le gestionnaire en tant que paramètre effectuerait en fait l'accès en lecture au délégué lui-même, qui rendrait la serrure à l'intérieur de la méthode inefficace. Est-ce un problème?
@Zakalwe: stocker la référence est atomique. Toutefois, deux threads pourraient tous les deux appeler Délégate.combine (qui est ce que + = se transforme), alors les deux pourraient lire la même valeur d'origine (x) et produire des valeurs différentes (Y et Z) chacune avec un délégué supplémentaire souscrit. Chacun affecterait ensuite la valeur au champ, entraînant l'un des gestionnaires «perdus».
@nobugz: Ensuite, vous dites que Jon Skeet est faux? Il dit, et je cite: Not Seul de nombreux livres et articles recommandent de verrouiller sur Ceci code>: le compilateur C # Le fait pour vous automatiquement si vous déclarez un événement sans spécifier le code ADD / Suppression. b>
@Aronner: J'aimerais juste souligner qu'il n'y a rien de mal à affirmer que je me trompe. Cela se produit et je ne veux pas que quiconque se sente "effrayé" de le prétendre. Dans ce cas particulier, j'ai raison (comme Nobugz a reconnu), mais il vaut toujours la peine d'avoir un aspirant si vous êtes en désaccord :)
@Jon: Voir ma réponse à votre autre commentaire!
@Jon Skeet: Bien sûr, et j'ai répondu à la question parce que je savais que les informations sont correctes, mais il était tellement plus facile de vous invoquer comme une autorité qu'il aurait été de discuter. :-P
@Zakalwe: Je pense que je peux surtout que celui-ci, si vous parlez de la commentaires de Mark Simpson. Les méthodes d'extension ne sont pas des méthodes d'instance réelles, comme en témoigne le fait que vous pouvez (avec succès) les invoquer sur une référence null en fonction de la façon dont vous les écrivez. Ils ne sont que du sucre syntaxique pour des méthodes statiques externes et je suis 99% positif que l'utilisation d'un ici sera pas b> contourner la serrure.
Votre réponse est incompatible. Étant donné que les délégués sont immuables et que l'affectation est atomique, le champ ne peut pas être dans un État incohérent.
Je me rends compte que je ne réponds pas à votre question, mais une approche plus simple d'éliminer la possibilité d'une exception de référence nulle lors de la levée d'un événement consiste à définir tous les événements égaux à la délégation {} sur le site de leur déclaration. Par exemple:
Je ne suis pas convaincu de la validité de la prise d'une copie pour éviter les nulls. L'événement devient NULL lorsque tous les abonnés disent à votre classe de ne pas leur parler. Em> null signifie personne ne veut entendre votre événement. STRUT> Peut-être que l'écoute de l'objet vient d'être disposée. Dans ce cas, la copie du gestionnaire vient de déplacer le problème. Au lieu de recevoir un appel à une null, vous recevez un appel à un gestionnaire d'événements qui a essayé de vous désabonner de l'événement. Appeler le gestionnaire copié déplace le problème de l'éditeur à l'abonné. Ma suggestion est juste de passer un essai / attraper autour de lui; p> J'ai aussi pensé que je «D Consultez comment Microsoft augmente l'événement le plus important de tous; en cliquant sur un bouton. Ils font juste cela, dans la base donc, ils copient le gestionnaire mais ne le verront pas. P > p> contrôler.onclick code>; p>
Vous préconisez attraper un NullReferenceException code>? Je vais devoir fortement i> en désaccord. Votre version jette une première chance
NullReferenceException code> Chaque fois que B> Le délégué est invoqué si personne n'est souscrit à celui-ci, et 90% des événements de contrôle ne sont jamais abonnés à! FYI, la version Microsoft que vous avez publiée utilise le
EventHandlerList Code> qui est déjà synchronisé, c'est pourquoi il n'a pas besoin d'un verrou explicit (les propriétés d'événement eux-mêmes n'utilisent pas de serrure spéciale).
Euh, non, je suggère seulement une nullreferenceException si le gestionnaire devient null après le chèque null i>. Pas pour chaque invocation. Le point était que la prise d'une copie du délégué garantit que vous rappelez des objets qui ont une explicité de ne pas être informé, ce qui est dangereux.
@Steve Cooper: Un problème majeur avec votre code est que vous n'envirez pas seulement non seulement une nullreferenceException résultant d'un gestionnaire devenant devenue de manière inattendue NULL, mais également toute nullreferenceException qui se produit dans l'exécution du gestionnaire lui-même.
Les événements "Fil-Safe" peuvent devenir assez compliqués. Il y a quelques problèmes différents que vous pourriez potentiellement courir dans:
Le dernier abonné peut se désabonner entre votre chèque NULL et appeler le délégué, provoquant une nullreferenceException. C'est une solution assez simple, vous pouvez soit verrouiller autour de l'appels (pas une excellente idée, car vous appelez code externe) p> copier le gestionnaire (recommandé) ou avoir un délégué nul qui est toujours souscrit. p> Notez que l'option 2 (copier le gestionnaire) ne peut pas Exiger un verrou - comme la copie est atomique, il n'y a aucune chance d'incohérences là-bas. P> Pour ramener ce retour à votre méthode d'extension, vous effectuez une légère variation de l'option 2. Votre copie se produit sur l'appel de la méthode de l'extension afin de pouvoir vous en tirer simplement: P > void Raise(this EventHandler handler, object sender, EventArgs e) {
if (handler != null) handler(sender, e);
}
void OnEvent(EventArgs e) {
// turns out, we don't really care what we lock on since
// we're using it for the implicit memory barrier,
// not synchronization
EventHandler h;
lock(new object()) {
h = this.SomeEvent;
}
h.Raise(this, e);
}
Merci de clarifier. J'ai soupçonné que le verrouillage de la méthode de l'extension pourrait être redondant, mais n'était pas tout à fait sûr. Ce que j'ai effectivement fait maintenant, c'est mélangé la méthode autour d'un peu pour accepter l'expéditeur émetteur i> comme ce code> et le gestionnaire en tant que paramètre
ref code> Aaronner suggéré. Pas tout aussi intuitif, mais il semble que cela fonctionne. J'ai mis à jour ma question avec le code que j'utilise.
Il y a plusieurs problèmes à portée de main ici, et je les traiterai un à la fois.
Tout d'abord, le code que vous avez dans votre question, il n'est pas nécessaire de verrouiller ce code. P>
En d'autres termes, la méthode d'augmentation peut être simplement réécrite à ceci: P>
Thread 1 Thread 2 read eventName read eventName add handler1 add handler2 write eventName write eventName <-- oops, handler1 disappeared
in c # nouvelle meilleure pratique est la suivante:
J'utilise une méthode comme celle-ci, mais je ne pense pas avoir besoin de la serrure. Vous pouvez également créer une surcharge sans éventuellement, ce qui passera evenarg.empty à l'événement que vous appelez.
En outre, vous n'avez peut-être même pas besoin de prendre une copie de la référence, car la méthode d'extension est elle-même adoptée une copie. Quelqu'un sache si c'est certainement le cas? (Inlinge, etc. pourrait potentiellement changer cela)
@Jonnii: J'ai été confondu à propos de l'utilisation de la serrure, comme l'exemple que je suis lié à suggère qu'un verrou est i> nécessaire. @Mark Simpson: Je considérais cela aussi, et cela m'a également eu pour moi que si la serrure est conçue pour synchroniser la copie (pour un but quelconque) et que, comme cela est fait pour appeler la méthode elle-même, la serrure ne serait pas efficace.
Je pense que vous êtes sur quelque chose, en fait. Les délégués ont une sémantique de type valeur afin que la méthode d'extension reçoit vraiment une copie. Ce n'est pas parce que c'est une méthode d'extension cependant; Même une méthode statique régulière recevrait une copie. Il y a un moyen autour de cela; Je vais éditer ma réponse.