我知道这与此问题略有重复:Blocking and waiting for an event
但是,我正在编写EventWaiter并遇到问题。这是我一直在努力的(主要)简化版本:
public class EventWaiter
{
private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private EventInfo _event = null;
private object _eventContainer = null;
public EventWaiter(object eventContainer, string eventName)
{
_eventContainer = eventContainer;
_event = eventContainer.GetType().GetEvent(eventName);
}
public void WaitForEvent()
{
MethodInfo method = this.GetType().GetMethod("DynamicCaller");
Delegate handler = Delegate.CreateDelegate(this._event.EventHandlerType, this, method);
_event.AddEventHandler(_eventContainer, handler);
_autoResetEvent.WaitOne();
_event.RemoveEventHandler(_eventContainer, _handler);
}
public void DynamicCaller(/* insert magic here */)
{
_autoResetEvent.Set();
}
}
用法只是:
EventWaiter ew = new EventWaiter(someClass, "someEvent");
ew.WaitForEvent();
基本上正在发生的事情是,它将DynamicCaller
void注册为此事件的处理程序。问题是,事件有不同的签名,我希望能够处理事件而不管使用的委托。
我可以使用this._event.EventHandlerType获取委托的类型,但是无论委托是什么,我如何使用它来创建一个完全可重用的类?如果DynamicCaller参数与事件委托参数不完全相同,则会出现异常。
作为旁注,我在框架中做了一堆查看代码,如果我可以访问其中一些,我认为这很容易。太糟糕了,我需要的很多类都是框架内部的。
答案 0 :(得分:2)
由于所有尊重推荐模式的事件都有一个object类型的参数和一个派生自EventArgs
的类型的参数,因此您应该能够使用此签名处理所有这些事件:
void DynamicCaller(object sender, EventArgs e)
当然它不适用于非标准事件签名...
编辑:这是一个动态生成处理程序的示例:
public class EventWaiter
{
private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private EventInfo _event = null;
private object _eventContainer = null;
public EventWaiter(object eventContainer, string eventName)
{
_eventContainer = eventContainer;
_event = eventContainer.GetType().GetEvent(eventName);
}
public void WaitForEvent()
{
Delegate handler = CreateHandler();
_event.AddEventHandler(_eventContainer, handler);
_autoResetEvent.WaitOne();
_event.RemoveEventHandler(_eventContainer, handler);
}
private Delegate CreateHandler()
{
var invokeMethod = _event.EventHandlerType.GetMethod("Invoke");
var invokeParameters = invokeMethod.GetParameters();
var handlerParameters = invokeParameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
var body = Expression.Call(Expression.Constant(_autoResetEvent), "Set", null);
var handlerExpression = Expression.Lambda(_event.EventHandlerType, body, handlerParameters);
return handlerExpression.Compile();
}
}
编辑:SLaks比我快;)
答案 1 :(得分:1)
您应该使用表达式树来编译一个方法,该方法具有一组调用您的回调的任意参数:
Expression.Lambda(
_event.EventHandlerType,
Expression.Call(Exrpession.Constant(_autoResetEvent),
typeof(AutoResetEvent).GetMethod("Set")),
_event.EventHandlerType.GetMethod("Invoke")
.GetParameters()
.Select(p => Expression.Parameter(p.ParameterType))
).Compile();
请注意,您可以使用泛型和表达式树使系统类型安全:
new EventWaiter(_ => someObject.SomeEvent += _)
_
是普通(但很短)的参数名称。
答案 2 :(得分:0)
您可以使用TaskCompletionSource
执行您想要的操作:
TaskCompletionSource<string> tcs =
new TaskCompletionSource<string>();
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, args) => {
if (args.Error != null) tcs.SetException(args.Error);
else if (args.Cancelled) tcs.SetCanceled();
else tcs.SetResult(args.Result);
};
client.DownloadStringAsync(address);
tcs.Task.Wait(); // WaitForEvent
答案 3 :(得分:0)
这里的解决方案很好,但对我来说,使用字符串,反射有点代码味道,所以我会选择通用版本:
public class EventWaiter
{
public enum Mode
{
Wait,
Detach
}
public static Func<Mode, TEventArgs> Create<TDelegate, TEventArgs>(
Func<Action<object, TEventArgs>, TDelegate> converter,
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler
)
{
AutoResetEvent semaphore = new AutoResetEvent(false);
TEventArgs args = default(TEventArgs);
TDelegate handler = converter((s, e) => { args = e; semaphore.Set(); });
addHandler(handler);
return mode =>
{
if (mode == Mode.Wait)
{
semaphore.WaitOne();
return args;
}
else
{
removeHandler(handler);
return default(TEventArgs);
}
};
}
用法:
var evt =
EventWaiter.Create<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>
(handler => (s, e) => handler(s, e),
h => port.DataReceived += h,
h => port.DataReceived -= h);
var firstArgument = evt(EventWaiter.Mode.Wait); //Wait for first event
var secondArgument = evt(EventWaiter.Mode.Wait); //Wait for second event
evt(EventWaiter.Mode.Detach); //Dispose