2012-08-12 43 views
3

我知道这是稍有这里这个问题的重复:Blocking and waiting for an event重用类等待事件触发

然而,我是在写一个EventWaiter的过程中,遇到了一个问题。这里是一个(majorly)简化了什么,我一直在努力版本:

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无效的此事件的处理程序。问题是,事件有不同的签名,我希望能够处理事件,而不管使用的代理。

我可以通过this._event.EventHandlerType获取委托类型,但是如何使用它创建一个完全可重用的类,而不管代理是什么?如果DynamicCaller参数与事件委托参数不完全相同,我会得到一个异常。作为一个便笺,我在框架中查了一大堆代码,如果我有权访问这些代码,我认为这很容易。太糟糕了,我需要的很多类都是框架内部的。

+0

我想你可能想看看TaskCompletionSource的http://毫秒dn.microsoft.com/en-us/library/dd449174。aspx – 2012-08-12 17:45:29

+0

@PeterRitchie:我看了一下,我可以看到它的实用性,但我没有看到这与此有关。它不能用于替代我的解决方案,它不能帮助解决我的问题。也许更多的信息,为什么你建议这将是有益的:) – caesay 2012-08-12 17:47:41

+0

我不明白你为什么不能模板委托类型? – Hogan 2012-08-12 17:56:09

回答

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 += _) 

_是一个普通(但短)参数名称。

+0

你可以举个例子吗? – caesay 2012-08-12 18:00:44

+0

@SLaks,传递lambda在这里不起作用,因为它不允许你检索EventInfo(至少不容易) – 2012-08-12 18:12:35

+0

@ThomasLevesque:正确。您需要走表达式树来获取实例和事件添加。 – SLaks 2012-08-12 18:19:24

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比我更快;)

+0

是和不是,虽然这是推荐模式,并且一切都应该遵循,但并非所有事情都会这样 - 这是问题的来源,因为我希望能够处理这种奇怪的情况。 – caesay 2012-08-12 17:54:55

+0

在这种情况下,我怕你唯一的选择是动态代码生成... – 2012-08-12 17:55:59

+0

可能使用DynamicMethod并发射它呢? – caesay 2012-08-12 17:57:31

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 
+0

这仍然与我试图做的事没有任何关系。 – caesay 2012-08-12 18:07:34

+0

你已经描述过你想等待事件发生。那段代码就是这样。如果您实际上不想等待事件发生,请您澄清一下。 – 2012-08-12 18:11:32

+0

我试图等待一个事件是的,但我想以一种可以抽象成辅助类的方式来做到这一点。 – caesay 2012-08-12 18:14:22

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