Mit dem neuen async/await-Modell ist es relativ einfach, eine Task
zu generieren, die abgeschlossen wird, wenn ein Ereignis ausgelöst wird. Sie müssen nur dieses Muster folgen:Universelle FromEvent-Methode
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;
}
Dies erlaubt dann:
await FromEvent(new MyClass());
Das Problem ist, dass Sie eine neue FromEvent
Methode für jedes Ereignis in jeder Klasse erstellen, die Sie möchten await
auf. Das könnte sehr schnell sehr groß werden, und es ist sowieso nur ein Vorabcode.
Im Idealfall würde Ich mag Lage sein, so etwas zu tun:
await FromEvent(new MyClass().OnCompletion);
Dann könnte ich die gleiche FromEvent
Methode für jeden Fall auf jeden Fall wiederverwenden. Ich habe einige Zeit damit verbracht, eine solche Methode zu erstellen, und es gibt eine Reihe von Hindernissen. Für den obigen Code den folgenden Fehler erzeugen:
The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=
Soweit ich das beurteilen kann, wird es nicht immer eine Möglichkeit, auf das Bestehen der Veranstaltung wie diese durch Code sein.
So schien die nächste beste Sache, zu versuchen, die Ereignisnamen als String zu übergeben:
await FromEvent(new MyClass(), "OnCompletion");
Es ist nicht so ideal; Sie erhalten kein intellisense und erhalten einen Laufzeitfehler, wenn das Ereignis für diesen Typ nicht existiert, aber es könnte immer noch nützlicher sein als Tonnen von FromEvent-Methoden.
So ist es einfach genug, Reflexion und GetEvent(eventName)
zu verwenden, um das EventInfo
Objekt zu erhalten. Das nächste Problem ist, dass der Delegat dieses Ereignisses zur Laufzeit nicht bekannt ist (und in der Lage sein muss, zu variieren). Das erschwert das Hinzufügen eines Ereignishandlers, da wir zur Laufzeit dynamisch eine Methode erstellen müssen, die eine bestimmte Signatur (die alle Parameter ignoriert) abgleicht, die auf eine TaskCompletionSource
zugreift, die wir bereits haben, und ihr Ergebnis setzt.
Glücklicherweise fand ich this link, die Anweisungen enthält, wie [fast] genau das über Reflection.Emit
zu tun. Jetzt ist das Problem, dass wir IL ausstrahlen müssen, und ich habe keine Ahnung, wie ich auf die tcs
Instanz zugreifen kann, die ich habe.
Unten finden Sie die Fortschritte, die ich diese in Richtung Fertigstellung gemacht haben:
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;
}
Was IL könnte möglicherweise emittieren ich, dass mir erlauben würde, das Ergebnis der TaskCompletionSource
zu setzen? Oder gibt es alternativ einen anderen Ansatz zum Erstellen einer Methode, die einen Task für ein beliebiges Ereignis von einem beliebigen Typ zurückgibt?
Beachten Sie, dass die BCL über 'TaskFactory.FromAsync' verfügt, um einfach von APM nach TAP zu übersetzen. Es gibt keine einfache * und * generische Möglichkeit, von EAP zu TAP zu übersetzen, deshalb denke ich, dass MS deshalb keine Lösung wie diese enthielt. Ich finde, dass Rx (oder TPL Dataflow) sowieso einer "event" -Semantik näher kommt - und Rx * hat eine 'FromEvent'-Methode. –
Ich wollte auch ein generisches 'FromEvent <>' machen, und [dies] (http://Stackoverflow.com/a/22798789/1768303) ist nah dran, wie ich das ohne Reflektion erreichen konnte. – Noseratio