2012-08-12 101 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