7
votes

Méthode d'objectif général

Utilisation du nouveau modèle ASYNC / AWAIT, il est assez simple de générer une tâche code> code> terminée lorsqu'un événement déclenche; Il vous suffit de suivre ce modèle:

public static Task FromEvent<T>(this T obj, string eventName)
{
    var tcs = new TaskCompletionSource<object>();
    var eventInfo = obj.GetType().GetEvent(eventName);

    Type eventDelegate = eventInfo.EventHandlerType;

    Type[] parameterTypes = GetDelegateParameterTypes(eventDelegate);
    DynamicMethod handler = new DynamicMethod("unnamed", null, parameterTypes);

    ILGenerator ilgen = handler.GetILGenerator();

    //TODO ilgen.Emit calls go here

    Delegate dEmitted = handler.CreateDelegate(eventDelegate);

    eventInfo.AddEventHandler(obj, dEmitted);

    return tcs.Task;
}


2 commentaires

Notez que le BCL a TaskFactory.fromasync pour traduire facilement de APM en appuyez sur. Il n'y a pas de manière facile et générique de traduire du PAE à Tap, alors je pense que c'est pourquoi MM n'a pas inclus une solution comme celle-ci. Je trouve RX (ou TPL Dataflow) pour être une correspondance plus proche de la sémantique "événement" de toute façon - et RX fait a un geventant type de méthode.


J'ai aussi voulu faire un générique <> , et Ce est proche comme je pouvais arriver à cela sans utiliser de réflexion.


4 Réponses :


2
votes

Si vous êtes prêt à avoir une méthode par type de délégué, vous pouvez faire quelque chose comme: xxx pré>

vous l'utiliseriez comme: p>

string s = await FromEvent<string>(x => c.OnCompletion += x);


2 commentaires

Le principal problème est que de nombreux cadres d'interface utilisateur créent leur propre type de délégué pour chaque événement (plutôt que d'utiliser action / EventHandler ), et C'est là que quelque chose comme celui-ci serait le plus utile, créant ainsi un fromevent pour chaque type de délégué serait mieux , mais toujours pas parfait. Cela dit, vous pourriez simplement avoir la première méthode que vous avez faite et utilisée: attendre devent (x => nouveau myclass (). Oncompletion + = (A, B) => x ((code> sur n'importe quel un événement. C'est une sorte de solution à mi-chemin.


@Servy ouais, j'ai pensé à le faire de cette façon aussi, mais je ne l'ai pas mentionné parce que je pense que c'est laid (i. Trop de chaudières).



5
votes

Cela vous donnera ce dont vous avez besoin sans avoir besoin de faire d'ilgen, et de plus simple. Cela fonctionne avec n'importe quel type de délégués d'événements; Il vous suffit de créer un gestionnaire différent pour chaque nombre de paramètres dans votre délégué d'événement. Vous trouverez ci-dessous les gestionnaires dont vous auriez besoin de 0..2, ce qui devrait être la grande majorité de vos cas d'utilisation. S'étendant à 3 et plus est une copie simple et une pâte de la méthode de 2 paramètres.

Ceci est également plus puissant que la méthode Ilgen, car vous pouvez utiliser toutes les valeurs créées par l'événement de votre motif asynchronisé. P>

static async void Run() {
    var result = await TaskFromEvent<int, string>(new MyClass(), "Fired");
    Console.WriteLine(result); // (123, "abcd")
}

public class MyClass {
    public delegate void TwoThings(int x, string y);

    public MyClass() {
        new Thread(() => {
            Thread.Sleep(1000);
            Fired(123, "abcd");
        }).Start();
    }

    public event TwoThings Fired;
}


1 commentaires

Thassk beaucoup !!! Pour Windows Phone, cette ligne doit être modifiée: var paramètres = méthodyinfo.getparameterers (). Sélectionnez (A => System.Linq.Expressions.expression.paramètre (A.ParameTTyètre (A.ParametType, A.Name))). TOARRAY ();



24
votes

Vous allez ici:

static async void Run() {
    object[] result = await new MyClass().FromEvent("Fired");
    Console.WriteLine(string.Join(", ", result.Select(arg =>
        arg.ToString()).ToArray())); // 123, abcd
}

public class MyClass {
    public delegate void TwoThings(int x, string y);

    public MyClass() {
        new Thread(() => {
                Thread.Sleep(1000);
                Fired(123, "abcd");
            }).Start();
    }

    public event TwoThings Fired;
}


5 commentaires

Je viens de finir de regarder par-dessus votre mise à jour et de jouer un peu. J'aime vraiment ça. Le gestionnaire d'événements est désabonné, ce qui est une grande touche. Les différents gestionnaires d'événements sont mis en cache, donc il n'est pas généré à plusieurs reprises pour les mêmes types et, contrairement à d'autres solutions, il n'est pas nécessaire de spécifier les types d'arguments au gestionnaire d'événements.


Je ne pouvais pas faire fonctionner le code sur Windows Phone, je ne sais pas si c'est une question de sécurité. Mais non travaillé .. Exception: {"Tentative d'accès à la méthode a échoué: System.Reflection.emit.dynamicmethod..ctor (system.type, system.type, system.type [], system.type)"}


@ J.Lennon Malheureusement, je ne suis pas capable de le tester sur le téléphone Windows. Donc, je serai vraiment reconnaissant si vous pouviez essayer d'utiliser ce Version mise à jour et laissez-moi savoir si cela aide. Merci d'avance.


@ J.Lennon Je pense que Servy a couvert que dans son commentaire ultérieur. Il existe probablement des différences de performance également (aucune idée qui serait plus rapide dans laquelle des scénarios sans profilage), mais des abonnements d'événements ou des déclencheurs sont peu susceptibles d'être un goulot d'étranglement de toute façon.


Savez-vous s'il serait possible de simplifier cela avec des arbres d'expression ou avez-vous besoin de l'ilgen de bas niveau?



-1
votes

J'ai fait face à ce problème en essayant d'écrire contrecatawaiter méthode d'extension pour system.action , oubliant que system.action est immutable et en passant comme argument, vous faites une copie. Cependant, vous ne faites pas une copie si vous le transmettez avec ref mot-clé, donc: xxx

utilisation: xxx

Remarque: l'auditeur TCS reste souscrit


2 commentaires

Cette méthode ne peut pas être utilisée pour attendre des événements d'autres classes. Il ne peut être utilisé qu'à partir de la classe actuelle. Sinon, vous obtiendrez une erreur de compilation: l'événement 'MyClass.OntionFinished' ne peut apparaître que sur le côté gauche de + = ou - = (sauf lorsqu'il est utilisé à partir de l'intérieur de type 'mycass')


Malheureusement donc. Ajout d'une ligne de code autreclass.onaction + = () => OnactionFinished? .Invoke (); avant Await était pratique dans mon cas.