Utilizzando il nuovo modello async/await è abbastanza semplice generare un Task
che viene completato quando si verifica un evento; non vi resta che seguire questo modello:Uso generico Metodo 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;
}
Questo permette quindi:
await FromEvent(new MyClass());
Il problema è che è necessario creare un nuovo metodo FromEvent
per ogni evento in ogni classe che si desidera await
sopra. Questo potrebbe diventare davvero grande molto velocemente, ed è per lo più solo codice boilerplate.
Idealmente mi piacerebbe essere in grado di fare qualcosa del genere:
await FromEvent(new MyClass().OnCompletion);
Poi ho potuto ri-utilizzare lo stesso metodo FromEvent
per qualsiasi evento in qualsiasi istanza. Ho passato un po 'di tempo a cercare di creare un tale metodo, e ci sono un certo numero di ostacoli. Per il codice di cui sopra verrà generato il seguente errore:
The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=
Per quanto posso dire, non ci sarà mai un modo di passare l'evento come questo tramite il codice.
Quindi, la cosa migliore sembrava essere cercando di passare il nome dell'evento come una stringa:
await FromEvent(new MyClass(), "OnCompletion");
Non è come l'ideale; non si ottiene intellisense e si otterrebbe un errore di runtime se l'evento non esiste per quel tipo, ma potrebbe essere ancora più utile di tonnellate di metodi FromEvent.
Quindi è abbastanza facile usare la riflessione e GetEvent(eventName)
per ottenere l'oggetto EventInfo
. Il prossimo problema è che il delegato di quell'evento non è noto (e deve essere in grado di variare) in fase di runtime. Questo rende difficile aggiungere un gestore di eventi, perché abbiamo bisogno di creare dinamicamente un metodo in fase di runtime, facendo corrispondere una determinata firma (ma ignorando tutti i parametri) che accede a uno TaskCompletionSource
che abbiamo già e ne imposta il risultato.
Fortunatamente ho trovato this link che contiene istruzioni su come eseguire [quasi] esattamente quello tramite Reflection.Emit
. Ora il problema è che abbiamo bisogno di emettere IL, e non ho idea di come accedere all'istanza tcs
che ho.
Di seguito i progressi che ho fatto nei confronti di finitura questo:
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;
}
Quello IL potrei emettere che mi avrebbe permesso di impostare il risultato della TaskCompletionSource
? Oppure, in alternativa, esiste un altro approccio alla creazione di un metodo che restituisce un'attività per qualsiasi evento arbitrario da un tipo arbitrario?
Si noti che il BCL ha 'TaskFactory.FromAsync' per tradurre facilmente da APM a TAP. Non esiste un modo semplice * e * generico per tradurre da EAP a TAP, quindi penso che sia per questo che MS non ha incluso una soluzione come questa. Trovo che Rx (o TPL Dataflow) sia più vicino alla semantica "evento" - e Rx * ha * un metodo di tipo 'FromEvent'. –
Volevo anche creare un generico 'FromEvent <>' e [this] (http://stackoverflow.com/a/22798789/1768303) è vicino a quello che potrei ottenere senza usare il reflection. – Noseratio