使用新的异步/等待模型,Task当事件触发时,生成一个已完成的相当简单。您只需要遵循以下模式:
Task
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; }
然后,这允许:
await FromEvent(new MyClass());
问题在于,您需要FromEvent为每个要在其上的类中的每个事件创建一个新方法await。那可能很快就变得非常大,反正大多只是样板代码。
FromEvent
await
理想情况下,我希望能够执行以下操作:
await FromEvent(new MyClass().OnCompletion);
然后,我可以FromEvent对任何实例上的任何事件重复使用相同的方法。我花了一些时间尝试创建这样的方法,并且有很多障碍。对于上面的代码,它将生成以下错误:
事件“ Namespace.MyClass.OnCompletion”只能出现在+ =或-=的左侧
据我所知,永远不会有通过代码传递这样的事件的方法。
因此,下一件最好的事情似乎是尝试将事件名称作为字符串传递:
await FromEvent(new MyClass(), "OnCompletion");
它不那么理想;如果该类型的事件不存在,则不会得到智能提示,并且会出现运行时错误,但它仍然比大量的FromEvent方法有用。
因此,使用反射并GetEvent(eventName)获取EventInfo对象非常容易。下一个问题是在运行时该事件的委托人未知(并且需要能够变化)。这使得添加事件处理程序变得很困难,因为我们需要在运行时动态创建一个方法,匹配一个访问TaskCompletionSource我们已经拥有的a并设置其结果的给定签名(但忽略所有参数)。
GetEvent(eventName)
EventInfo
TaskCompletionSource
幸运的是,我找到了此链接,其中包含有关如何通过几乎完全执行操作的说明Reflection.Emit。现在的问题是我们需要发出IL,而且我不知道如何访问tcs我拥有的实例。
Reflection.Emit
tcs
以下是我在完成此操作方面所取得的进展:
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; }
我可以发出什么IL来设置结果TaskCompletionSource?或者,是否存在另一种方法来创建为任意类型的任意事件返回Task的方法?
干得好:
internal class TaskCompletionSourceHolder { private readonly TaskCompletionSource<object[]> m_tcs; internal object Target { get; set; } internal EventInfo EventInfo { get; set; } internal Delegate Delegate { get; set; } internal TaskCompletionSourceHolder(TaskCompletionSource<object[]> tsc) { m_tcs = tsc; } private void SetResult(params object[] args) { // this method will be called from emitted IL // so we can set result here, unsubscribe from the event // or do whatever we want. // object[] args will contain arguments // passed to the event handler m_tcs.SetResult(args); EventInfo.RemoveEventHandler(Target, Delegate); } } public static class ExtensionMethods { private static Dictionary<Type, DynamicMethod> s_emittedHandlers = new Dictionary<Type, DynamicMethod>(); private static void GetDelegateParameterAndReturnTypes(Type delegateType, out List<Type> parameterTypes, out Type returnType) { if (delegateType.BaseType != typeof(MulticastDelegate)) throw new ArgumentException("delegateType is not a delegate"); MethodInfo invoke = delegateType.GetMethod("Invoke"); if (invoke == null) throw new ArgumentException("delegateType is not a delegate."); ParameterInfo[] parameters = invoke.GetParameters(); parameterTypes = new List<Type>(parameters.Length); for (int i = 0; i < parameters.Length; i++) parameterTypes.Add(parameters[i].ParameterType); returnType = invoke.ReturnType; } public static Task<object[]> FromEvent<T>(this T obj, string eventName) { var tcs = new TaskCompletionSource<object[]>(); var tcsh = new TaskCompletionSourceHolder(tcs); EventInfo eventInfo = obj.GetType().GetEvent(eventName); Type eventDelegateType = eventInfo.EventHandlerType; DynamicMethod handler; if (!s_emittedHandlers.TryGetValue(eventDelegateType, out handler)) { Type returnType; List<Type> parameterTypes; GetDelegateParameterAndReturnTypes(eventDelegateType, out parameterTypes, out returnType); if (returnType != typeof(void)) throw new NotSupportedException(); Type tcshType = tcsh.GetType(); MethodInfo setResultMethodInfo = tcshType.GetMethod( "SetResult", BindingFlags.NonPublic | BindingFlags.Instance); // I'm going to create an instance-like method // so, first argument must an instance itself // i.e. TaskCompletionSourceHolder *this* parameterTypes.Insert(0, tcshType); Type[] parameterTypesAr = parameterTypes.ToArray(); handler = new DynamicMethod("unnamed", returnType, parameterTypesAr, tcshType); ILGenerator ilgen = handler.GetILGenerator(); // declare local variable of type object[] LocalBuilder arr = ilgen.DeclareLocal(typeof(object[])); // push array's size onto the stack ilgen.Emit(OpCodes.Ldc_I4, parameterTypesAr.Length - 1); // create an object array of the given size ilgen.Emit(OpCodes.Newarr, typeof(object)); // and store it in the local variable ilgen.Emit(OpCodes.Stloc, arr); // iterate thru all arguments except the zero one (i.e. *this*) // and store them to the array for (int i = 1; i < parameterTypesAr.Length; i++) { // push the array onto the stack ilgen.Emit(OpCodes.Ldloc, arr); // push the argument's index onto the stack ilgen.Emit(OpCodes.Ldc_I4, i - 1); // push the argument onto the stack ilgen.Emit(OpCodes.Ldarg, i); // check if it is of a value type // and perform boxing if necessary if (parameterTypesAr[i].IsValueType) ilgen.Emit(OpCodes.Box, parameterTypesAr[i]); // store the value to the argument's array ilgen.Emit(OpCodes.Stelem, typeof(object)); } // load zero-argument (i.e. *this*) onto the stack ilgen.Emit(OpCodes.Ldarg_0); // load the array onto the stack ilgen.Emit(OpCodes.Ldloc, arr); // call this.SetResult(arr); ilgen.Emit(OpCodes.Call, setResultMethodInfo); // and return ilgen.Emit(OpCodes.Ret); s_emittedHandlers.Add(eventDelegateType, handler); } Delegate dEmitted = handler.CreateDelegate(eventDelegateType, tcsh); tcsh.Target = obj; tcsh.EventInfo = eventInfo; tcsh.Delegate = dEmitted; eventInfo.AddEventHandler(obj, dEmitted); return tcs.Task; } }
这段代码适用于几乎所有返回void的事件(无论参数列表如何)。
如有必要,可以对其进行改进以支持任何返回值。
您可以在下面看到Dax方法和mine方法之间的区别:
static async void Run() { object[] result = await new MyClass().FromEvent("Fired"); Console.WriteLine(string.Join(", ", result.Select(arg => arg.ToString()).ToArray())); // 123, abcd } public class MyClass { public delegate void TwoThings(int x, string y); public MyClass() { new Thread(() => { Thread.Sleep(1000); Fired(123, "abcd"); }).Start(); } public event TwoThings Fired; }
简而言之,我的代码 实际上 支持任何类型的委托类型。您不应该(也不需要)像那样明确指定它TaskFromEvent<int, string>。
TaskFromEvent<int, string>