1
votes

comment attendre un appel de gestionnaire d'événements?

en gros, j'ai une méthode qui appelle un gestionnaire d'événements. le gestionnaire d'événements appelle une méthode asynchrone et j'ai besoin de connaître les résultats de cette méthode (vrai ou faux). comme les gestionnaires d'événements ne peuvent renvoyer que void, j'ai créé mes propres eventargs avec une propriété success que j'ai définie sur true dans cette méthode si cela fonctionne correctement.

private async void SomeEventHandler(object sender, MyEventArgs e)
        {
            e.Success = await AnAsyncMethod();
        }

private asyc Task<bool> AnAsyncMethod()
{
  //...
}

SomeEvent est connecté à SomeEventHandler p >

public virtual async Task<bool> TrySomething()
        {
            var args = new MyEventArgs();
            SomeEvent?.Invoke(this, args);
            return args.Success;
        }

Ce qui me trouble, c'est que, y a-t-il une garantie que la méthode TrySomething attendra que le SomeEvent se termine donc Success a été défini avant de le renvoyer? Et sinon, comment puis-je m'assurer que c'est le cas?

Merci


2 commentaires

Pourquoi ne pas simplement utiliser une tâche de suivi?


async void est toujours une mauvaise nouvelle en termes de wait ; franchement, les événements ne sont pas bien adaptés à une utilisation avec async ; vous pourriez être en mesure de le pirater comme un événement Func <..., Task> ... , mais cela pourrait ne pas bien fonctionner avec plusieurs abonnés, etc. et non: pour être explicite, il n'y a pas de garanties asynchrones dans cet exemple: SomeEventHandler retournera dès que l'opération en attente incomplète se produit, ou après que tout soit terminé si rien n'est incomplet


3 Réponses :


2
votes

La raison pour laquelle async void est autorisé concerne les gestionnaires d'événements. À partir de la documentation sur Types de retour asynchrone :

Vous utilisez le type de retour void dans les gestionnaires d'événements asynchrones, qui nécessitent un type de retour void.

Cela dit, les événements sont conçus pour "signaler l'occurrence d'une action" à toute partie intéressée, le cas échéant (et il peut y en avoir aucune, ou plusieurs). Pensez-y comme envoyer une notification par e-mail à quiconque est abonné à une liste de diffusion spécifique: ces personnes peuvent avoir à faire quelque chose lorsqu'elles reçoivent la notification, mais ce n'est pas votre préoccupation - vous passez à votre tâche suivante après avoir envoyé l'e-mail. Ce sont tous les événements. Un gestionnaire d'événements ne doit pas être quelque chose d'important pour le bon fonctionnement de l'objet.

Les événements ne sont pas conçus pour être un "hé c'est arrivé, que dois-je faire ensuite?" Donc, ne devrait pas avoir besoin d’attendre un gestionnaire d’événements.

Si le retour de args.Success dépend de la réussite de SomeEventHandler , alors il ne devrait pas s'agir d'un gestionnaire d'événements. Au lieu de cela, vous pouvez avoir une propriété Func> (une fonction qui renvoie Task ). Quelque chose comme ceci:

// This is the method we want it to call
private async Task<bool> AnAsyncMethod() {
  await Task.Delay(1);
  return true;
}

// so we pass it in the constructor of the class.
// You don't have to pass it in the constructor - this is just an example
var myClass = new SomeClass(AnAsyncMethod);

Ensuite, vous pouvez l'utiliser comme ceci:

public class SomeClass {
    private Func<Task<bool>> IsSuccessful;

    public SomeClass(Func<Task<bool>> isSuccessful) {
        // Accept a reference to a function and store it
        IsSuccessful = isSuccessful;
    }

    public async Task<bool> DoSomething() {
        // Call our function and return the result
        return await IsSuccessful();
    }
}

De cette façon, il est très clair que SomeClass ne peut pas fonctionner correctement sans appeler cette méthode et donc exactement une implémentation de cette méthode doit être transmise à la classe.

p>


0 commentaires

1
votes

Vous pouvez déclarer la propriété Success comme Task au lieu de bool . Attribuez-le ensuite dans le gestionnaire comme ceci:

public virtual async Task<bool> TrySomething()
{
    var args = new MyEventArgs();
    SomeEvent?.Invoke(this, args);
    return await (args.Success ?? Task.FromResult(false));
}

À la fin, vous pourrez obtenir le résultat après avoir appelé l'événement et attendu la propriété.

private void SomeEventHandler(object sender, MyEventArgs e)
{
    e.Success = AnAsyncMethod(); // Without await
}


0 commentaires

2
votes

Y a-t-il une garantie que la méthode TrySomething attendra que l'événement SomeEvent se termine donc Success a été défini avant de le renvoyer?

Non. async void signifie "ne pas avertir l'appelant lorsque j'ai terminé". Ainsi, le code qui déclenche votre événement ne peut pas savoir quand le gestionnaire d'événements est terminé, à moins que vous n'écriviez vous-même cette logique supplémentaire.

Et sinon, comment puis-je m'assurer que c'est le cas?

Eh bien, c'est plus une question délicate. Les événements .NET sont conçus comme ce que j'appelle " événements de notification "- c'est-à-dire que lorsqu'un événement se déclenche, il notifie tous ses écouteurs. Il n'est pas nécessaire d '«attendre» car il n'y a aucun moyen pour un auditeur de fournir des commentaires au notifiant.

Votre code est un exemple de ce que j'appelle un "événement de commande" - code qui est un événement mais ne correspond pas à la sémantique d'un événement de notification. Votre code demande une réponse du gestionnaire.

Donc, la première question que vous devez vous poser est "est-ce que je veux vraiment que ce soit un événement?" Un bon test décisif pour cela est "puis-je définir une sémantique significative s'il y a plusieurs gestionnaires?"

Plus précisément, comment votre code devrait-il se comporter si plusieurs gestionnaires sont connectés à l'événement? Peut-être que la réponse est "cela n'aurait pas de sens". Ou peut-être que la réponse est "Je veux attendre que tous d’entre eux se terminent et qu’ils ne réussissent que si tous sont« réussis ». Ou "attendez tout et soyez" réussi "si l'un d'entre eux est" réussi "". Ou "attendez que le premier se termine et utilisez ce résultat". Ou "attendez qu'ils terminent un par un, en s'arrêtant au premier succès". Ou "attendez qu'ils se terminent un par un, en s'arrêtant au premier échec". Ce sont les choix qui viennent immédiatement à l'esprit; il pourrait y en avoir plus.

Si la réponse est "cela ne se produira pas dans mon code" ou "plus d'un gestionnaire n'a pas de sens" ou "c'est une décision trop difficile à prendre maintenant", alors la réponse appropriée est de < em> supprimez le event . Ce n'est pas un événement. C'est un appel de méthode. Dans la terminologie Design Pattern, les événements sont utilisés pour implémenter le Observer Pattern < / a>, mais vous disposez d'un modèle de stratégie , et c'est pourquoi Les événements sont mal adaptés. Dans ce cas, vous pouvez utiliser la réponse de Gabriel ou quelque chose de similaire où vous définissez la stratégie à l'aide d'une interface, et au lieu d'élever un événement où vous invoqueriez une méthode sur cette interface.

Cependant, si cela est logique d'avoir plusieurs gestionnaires, et qu'il y a une sémantique significative que vous pouvez utiliser, alors vous devrez modifier le EventArgs tapez pour avoir une sorte de "collecteur" de réponses, puis demandez à votre code de déclenchement d'événements d'interpréter ces réponses. Le code exact varie en fonction de la sémantique dont vous avez besoin.


0 commentaires