Dzięki nowemu modelowi async/await można wygenerować Task
, która jest wykonywana po uruchomieniu zdarzenia; po prostu trzeba się do tego wzoru:Ogólna metoda 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;
}
Pozwala to następnie:
await FromEvent(new MyClass());
Problem polega na tym, że trzeba stworzyć nową metodę FromEvent
dla każdego zdarzenia w każdej klasie, które chcieliby Państwo await
na. To może stać się naprawdę duże naprawdę szybkie, a to w większości przypadków tylko kodowany kod.
Idealnie chciałbym być w stanie zrobić coś takiego:
await FromEvent(new MyClass().OnCompletion);
Potem może ponownie użyć tej samej metody FromEvent
dla każdego zdarzenia w każdym przypadku. Spędziłem trochę czasu, próbując stworzyć taką metodę, i istnieje wiele przeszkód. Na powyższy kod to wygeneruje następujący błąd:
The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=
O ile mogę powiedzieć, że nigdy nie będzie to sposób przekazywania zdarzenie jak to poprzez kod.
Tak więc, następnym najlepszą rzeczą wydawało się być stara się przekazać nazwę zdarzenia jako wyrażenie:
await FromEvent(new MyClass(), "OnCompletion");
To nie jest tak idealny; nie dostaniesz intellisense i otrzymasz błąd runtime, jeśli zdarzenie nie istnieje dla tego typu, ale może być bardziej przydatne niż tony metod FromEvent.
Łatwo jest użyć odbicia i GetEvent(eventName)
, aby uzyskać obiekt EventInfo
. Kolejnym problemem jest to, że delegat tego wydarzenia nie jest znany (i musi być w stanie zmieniać) w czasie wykonywania. To sprawia, że dodanie obsługi zdarzenia jest trudne, ponieważ musimy dynamicznie tworzyć metodę w czasie wykonywania, dopasowując dany podpis (ale ignorując wszystkie parametry), który uzyskuje dostęp do TaskCompletionSource
, który już mamy i ustawia jego wynik.
Na szczęście znalazłem this link, który zawiera instrukcje jak wykonać [prawie] dokładnie to poprzez Reflection.Emit
. Problem polega na tym, że musimy emitować IL i nie mam pojęcia, jak uzyskać dostęp do instancji tcs
, którą mam.
Poniżej jest postęp, jaki zrobiłem w kierunku kończąc w ten sposób:
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;
}
Co IL mógłbym emitować że pozwoli mi ustawić wynik TaskCompletionSource
? Lub alternatywnie, czy istnieje inne podejście do tworzenia metody zwracającej zadanie dla dowolnego zdarzenia z dowolnego typu?
Należy zauważyć, że BCL ma 'TaskFactory.FromAsync', aby łatwo przetłumaczyć z APM na TAP. Nie ma łatwego * i * ogólnego sposobu tłumaczenia z EAP na TAP, więc myślę, że właśnie dlatego MS nie zawierało takiego rozwiązania. Uważam, że Rx (lub TPL Dataflow) jest bliższy semantyce "eventowej" - a Rx * ma * rodzaj metody "FromEvent". –
Chciałem również zrobić ogólne 'FromEvent <>', i [this] (http://stackoverflow.com/a/22798789/1768303) jest blisko, jak mogłem dostać się do tego bez użycia refleksji. – Noseratio