En utilisant le nouveau modèle async/await, il est assez simple de générer un Task
qui est terminé lorsqu'un événement se déclenche; il vous suffit de suivre ce modèle:Méthode générale FromEvent
public class MyClass
{
public event Action OnCompletion;
}
public static Task FromEvent(MyClass obj)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
obj.OnCompletion +=() =>
{
tcs.SetResult(null);
};
return tcs.Task;
}
Ceci permet alors:
await FromEvent(new MyClass());
Le problème est que vous devez créer une nouvelle méthode FromEvent
pour chaque événement dans toutes les classes que vous souhaitez await
sur. Cela pourrait être vraiment très rapide, et c'est surtout du code standard.
Idéalement je voudrais être en mesure de faire quelque chose comme ceci:
await FromEvent(new MyClass().OnCompletion);
alors je pourrais réutiliser la même méthode FromEvent
pour tout événement sur une instance. J'ai passé du temps à essayer de créer une telle méthode, et il y a un certain nombre de problèmes. Pour le code ci-dessus, il va générer l'erreur suivante:
The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=
Pour autant que je sache, il n'y aura jamais un moyen de passer l'événement comme celui-ci par le code.
Donc, la meilleure chose semblait essayer de passer le nom de l'événement comme une chaîne:
await FromEvent(new MyClass(), "OnCompletion");
Ce n'est pas comme idéal; vous n'obtenez pas intellisense et vous obtiendrez une erreur d'exécution si l'événement n'existe pas pour ce type, mais cela pourrait être plus utile que des tonnes de méthodes FromEvent.
Il est donc assez facile d'utiliser la réflexion et GetEvent(eventName)
pour obtenir l'objet EventInfo
. Le problème suivant est que le délégué de cet événement n'est pas connu (et doit pouvoir varier) à l'exécution. Cela rend difficile l'ajout d'un gestionnaire d'événements, car nous devons créer dynamiquement une méthode à l'exécution, en faisant correspondre une signature donnée (mais en ignorant tous les paramètres) qui accède à un TaskCompletionSource
que nous avons déjà et en définit le résultat.
Heureusement, j'ai trouvé this link qui contient des instructions sur la façon de faire [presque] exactement cela via Reflection.Emit
. Maintenant, le problème est que nous devons émettre IL, et je n'ai aucune idée comment accéder à l'instance tcs
que j'ai.
est Ci-dessous les progrès que je l'ai fait pour finir ceci:
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;
}
Quelle IL pourrais-je émettre éventuellement qui me permettrait de mettre en résultat de la TaskCompletionSource
? Ou, alternativement, y a-t-il une autre approche pour créer une méthode qui renvoie une tâche pour n'importe quel événement arbitraire d'un type arbitraire?
Notez que le BCL a 'TaskFactory.FromAsync' pour traduire facilement APM en TAP. Il n'y a pas de moyen simple et * générique de traduire d'EAP en TAP, donc je pense que c'est pourquoi MS n'a pas inclus une solution comme celle-ci. Je trouve que Rx (ou TPL Dataflow) est de plus en plus proche de la sémantique "event" - et Rx * a une méthode de type FromEvent. –
Je voulais aussi faire un 'FromEvent <>' générique, et [this] (http://stackoverflow.com/a/22798789/1768303) est proche car je pourrais y arriver sans utiliser de réflexion. – Noseratio