Usando el nuevo modelo async/await es bastante sencillo generar un Task
que se completa cuando se produce un evento; sólo tiene que seguir este patrón:De uso general Método 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;
}
Esto permite:
await FromEvent(new MyClass());
El problema es que se necesita para crear un nuevo método FromEvent
para cada evento en cada clase que le gustaría await
en. Eso podría ser muy grande realmente rápido, y de todos modos es solo un código repetitivo.
Idealmente me gustaría ser capaz de hacer algo como esto:
await FromEvent(new MyClass().OnCompletion);
Entonces podría volver a utilizar el mismo método FromEvent
para cualquier evento en cualquier instancia. He pasado un tiempo tratando de crear un método así, y hay una serie de inconvenientes. Para el código anterior generará el siguiente error:
The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=
Por lo que yo puedo decir, no habrá nunca una forma de pasar el evento como este a través de código.
Por lo tanto, la siguiente mejor cosa parecía estar tratando de pasar el nombre del evento como una cadena:
await FromEvent(new MyClass(), "OnCompletion");
No es tan ideales; no obtiene intellisense y obtendría un error de tiempo de ejecución si el evento no existe para ese tipo, pero aún podría ser más útil que un montón de métodos FromEvent.
Por lo tanto, es bastante fácil utilizar el reflejo y GetEvent(eventName)
para obtener el objeto EventInfo
. El siguiente problema es que el delegado de ese evento no se conoce (y debe poder variar) en tiempo de ejecución. Eso hace que sea difícil agregar un controlador de eventos, porque necesitamos crear dinámicamente un método en tiempo de ejecución, que coincida con una firma determinada (pero ignorando todos los parámetros) que tiene acceso a un TaskCompletionSource
que ya tenemos y establece su resultado.
Afortunadamente encontré this link que contiene instrucciones sobre cómo hacer [casi] exactamente eso a través de Reflection.Emit
. Ahora el problema es que necesitamos emitir IL, y no tengo idea de cómo acceder a la instancia tcs
que tengo.
A continuación se muestra el progreso que he hecho a terminar esto:
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;
}
Lo IL podría yo emitir que permitiría que fije el resultado de la TaskCompletionSource
? O, alternativamente, ¿existe otro enfoque para crear un método que devuelva una Tarea para cualquier evento arbitrario de un tipo arbitrario?
Tenga en cuenta que el BCL tiene 'TaskFactory.FromAsync' para traducir fácilmente de APM a TAP. No hay una manera fácil * y * de traducir de EAP a TAP, así que creo que es por eso que MS no incluyó una solución como esta. Encuentro que Rx (o TPL Dataflow) está más cerca de la semántica de "eventos" de todos modos, y Rx * does * tiene un tipo de método 'FromEvent'. –
También quería crear un 'FromEvent <>' genérico, y [this] (http://stackoverflow.com/a/22798789/1768303) está más cerca de lo que podría llegar sin usar el reflejo. – Noseratio