10
votes

Est-ce un modèle valide pour élever des événements en C #?

Mise à jour forte>: au profit de toute personne qui lise cela, car .NET 4, la verrouille est inutile en raison de la synchronisation des événements générés automatiquement, donc je viens de l'utiliser maintenant:

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);
    }
}


4 commentaires

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 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.


7 Réponses :


1
votes
lock (@lock)
{
    handlerCopy = handler;
}
Assignment of basic types like a references are atomic, so there is no point of using a lock here.

3 commentaires

Alors, pourquoi l'exemple de Jon Skeet utilise-t-il une serrure? Il mentionne également que le Ajouter / Supprimer 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 + = et - = (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 + = ou - = , la méthode de l'extension Je ne saurai pas à ce sujet, n'est-ce pas?



7
votes

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);
    }
}


8 commentaires

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 est atomique, mais qu'en est-il de _click + = valeur _cliquez - valeur ? 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 : le compilateur C # Le fait pour vous automatiquement si vous déclarez un événement sans spécifier le code ADD / Suppression.


@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 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.



2
votes

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: xxx


0 commentaires

-2
votes

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. null signifie personne ne veut entendre votre événement. 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; xxx

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 contrôler.onclick ; xxx

donc, ils copient le gestionnaire mais ne le verront pas.


3 commentaires

Vous préconisez attraper un NullReferenceException ? Je vais devoir fortement en désaccord. Votre version jette une première chance NullReferenceException Chaque fois que 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 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 . 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.



1
votes

Les événements "Fil-Safe" peuvent devenir assez compliqués. Il y a quelques problèmes différents que vous pourriez potentiellement courir dans:

NullReferenceException h3>

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> xxx pré>

copier le gestionnaire (recommandé) xxx pré>

ou avoir un délégué nul qui est toujours souscrit. p> xxx pré>

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);
}


1 commentaires

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 comme ce et le gestionnaire en tant que paramètre ref 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.



0
votes

Il y a plusieurs problèmes à portée de main ici, et je les traiterai un à la fois.

Numéro n ° 1: Votre code, avez-vous besoin de verrouiller? H2>

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


0 commentaires

4
votes

in c # nouvelle meilleure pratique est la suivante: xxx

Vous pouvez voir cet article.


0 commentaires