2009-01-29 203 views
35

它有時想在等待事件發生時阻止我的線程。阻止並等待事件

我平時做這樣的事情:

private AutoResetEvent _autoResetEvent = new AutoResetEvent(false); 

private void OnEvent(object sender, EventArgs e){ 
    _autoResetEvent.Set(); 
} 

// ... 
button.Click += OnEvent; 
try{ 
    _autoResetEvent.WaitOne(); 
} 
finally{ 
    button.Click -= OnEvent; 
} 

但是,看來這應該是我能解壓到一個公共類(甚至一些已經存在於框架)。

我希望能夠做這樣的事情:

EventWaiter ew = new EventWaiter(button.Click); 
ew.WaitOne(); 
EventWaiter ew2 = new EventWaiter(form.Closing); 
ew2.WaitOne(); 

但我真的不能找到一種方法來構建這樣一個類(我無法找到傳遞一個很好的有效途徑事件作爲參數)。誰能幫忙?

舉爲什麼這可能是有用的一個例子,考慮這樣的事情:

var status = ShowStatusForm(); 
status.ShowInsertUsbStick(); 
bool cancelled = WaitForUsbStickOrCancel(); 
if(!cancelled){ 
    status.ShowWritingOnUsbStick(); 
    WriteOnUsbStick(); 
    status.AskUserToRemoveUsbStick(); 
    WaitForUsbStickToBeRemoved(); 
    status.ShowFinished(); 
}else{ 
    status.ShowCancelled(); 
} 
status.WaitUntilUserPressesDone(); 

這比用多種方法之間展開的邏輯編寫的等效代碼更加簡明易讀。但要實現WaitForUsbStickOrCancel(),WaitForUsbStickToBeRemoved和WaitUntilUserPressesDone()(假設我們在插入或移除usb棒時得到一個事件),我需要每次都重新實現「EventWaiter」。當然,你必須小心,永遠不要在GUI線程上運行它,但有時這對於簡單代碼來說是值得的折衷。

另一種選擇將是這個樣子:

var status = ShowStatusForm(); 
status.ShowInsertUsbStick(); 
usbHandler.Inserted += OnInserted; 
status.Cancel += OnCancel; 
//... 
void OnInserted(/*..*/){ 
    usbHandler.Inserted -= OnInserted; 
    status.ShowWritingOnUsbStick(); 
    MethodInvoker mi =() => WriteOnUsbStick(); 
    mi.BeginInvoke(WritingDone, null); 
} 
void WritingDone(/*..*/){ 
    /* EndInvoke */ 
    status.AskUserToRemoveUsbStick(); 
    usbHandler.Removed += OnRemoved; 
} 
void OnRemoved(/*..*/){ 
    usbHandler.Removed -= OnRemoved; 
    status.ShowFinished(); 
    status.Done += OnDone; 
} 
/* etc */ 

我發現更難閱讀。不可否認的是,這種流動的線性是非常直線的,但事實並非如此,我喜歡第一種風格。

這與使用ShowMessage()和Form.ShowDialog()類似 - 它們也會阻塞,直到發生某些「事件」(儘管它們將在gui線程上調用時運行消息循環)。

+0

我很好奇,到底爲什麼你會想這樣做......你能解釋一下? – 2009-01-30 00:07:10

+0

我仍然不明白爲什麼你想要阻止線程而不是等待事件 - 這是什麼完成的? – 2009-01-30 06:54:54

+0

你有沒有解決這個問題?我有同樣的問題。 – Carlo 2011-10-18 00:40:16

回答

3

不要傳遞事件,傳遞與事件處理函數簽名匹配的委託。這對我來說實際上聽起來很詭異,所以要注意潛在的死鎖問題。

-7

我覺得像這些應該工作,沒有試過剛剛編碼。

public class EventWaiter<T> where T : EventArgs 
{ 
    private System.Threading.ManualResetEvent manualEvent; 

    public EventWaiter(T e) 
    { 
     manualEvent = new System.Threading.ManualResetEvent(false); 
     e += this.OnEvent; 
    } 

    public void OnEvent(object sender, EventArgs e) 
    { 
     manualEvent.Set(); 
    } 

    public void WaitOne() 
    { 
     manualEvent.WaitOne(); 
    } 

    public void Reset() 
    { 
     manualEvent.Reset(); 
    } 
} 

沒有想過太多,但無法弄清楚如何使它與EventArgs隔離。

看一看MSDN ManualResetEvent,你會發現你可以鏈接等待和一些奇怪的東西。

0

我已經在LinqPad中使用反射衝撞了一個工作示例,使用一個字符串獲取對EventInfo對象的引用(當您鬆散編譯時檢查時要小心)。顯而易見的問題是,沒有保證,事件將被解僱,或者您預期的事件可能會在EventWaiter類準備好開始阻止之前被解僱,所以我不確定如果我把它放在這裏,我會睡得很舒服生產應用程序。

void Main() 
{ 
    Console.WriteLine("main thread started"); 

    var workerClass = new WorkerClassWithEvent(); 
    workerClass.PerformWork(); 

    var waiter = new EventWaiter(workerClass, "WorkCompletedEvent"); 
    waiter.WaitForEvent(TimeSpan.FromSeconds(10)); 

    Console.WriteLine("main thread continues after waiting"); 
} 

public class WorkerClassWithEvent 
{ 
    public void PerformWork() 
    { 
     var worker = new BackgroundWorker(); 
     worker.DoWork += (s, e) => 
     { 
      Console.WriteLine("threaded work started"); 
      Thread.Sleep(1000); // <= the work 
      Console.WriteLine("threaded work complete"); 
     }; 
     worker.RunWorkerCompleted += (s, e) => 
     { 
      FireWorkCompletedEvent(); 
      Console.WriteLine("work complete event fired"); 
     }; 

     worker.RunWorkerAsync(); 
    } 

    public event Action WorkCompletedEvent; 
    private void FireWorkCompletedEvent() 
    { 
     if (WorkCompletedEvent != null) WorkCompletedEvent(); 
    } 
} 

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(TimeSpan timeout) 
    { 
     _event.AddEventHandler(_eventContainer, (Action)delegate { _autoResetEvent.Set(); }); 
     _autoResetEvent.WaitOne(timeout); 
    } 
} 

輸出

// main thread started 
// threaded work started 
// threaded work complete 
// work complete event fired 
// main thread continues after waiting 
3

我改性死。Rabit的類EventWaiter來處理EventHandler<T>。因此,您可以使用等待所有事件類型EventHandler<T>,這意味着您的代表是delegate void SomeDelegate(object sender, T EventsArgs)

public class EventWaiter<T> 
{ 

    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(TimeSpan timeout) 
    { 
     EventHandler<T> eventHandler = new EventHandler<T>((sender, args) => { _autoResetEvent.Set(); }); 
     _event.AddEventHandler(_eventContainer, eventHandler); 
     _autoResetEvent.WaitOne(timeout); 
     _event.RemoveEventHandler(_eventContainer, eventHandler); 
    } 
} 

例如,我使用它來等待從HttpNotificationChannel獲取Url,當我註冊到Windows推送通知服務時。

  HttpNotificationChannel pushChannel = new HttpNotificationChannel(channelName); 
      //ChannelUriUpdated is event 
      EventWaiter<NotificationChannelUriEventArgs> ew = new EventWaiter<NotificationChannelUriEventArgs>(pushChannel, "ChannelUriUpdated"); 
      pushChannel.Open(); 
      ew.WaitForEvent(TimeSpan.FromSeconds(30)); 
0

你也可以試試這個:

class EventWaiter<TEventArgs> where TEventArgs : EventArgs 
{ 
    private readonly Action<EventHandler<TEventArgs>> _unsubHandler; 
    private readonly Action<EventHandler<TEventArgs>> _subHandler; 

    public EventWaiter(Action<EventHandler<TEventArgs>> subHandler, Action<EventHandler<TEventArgs>> unsubHandler) 
    { 
     _unsubHandler = unsubHandler; 
     _subHandler = subHandler; 
    } 

    protected void Handler(object sender, TEventArgs args) 
    { 
     _unsubHandler.Invoke(Handler); 
     TaskCompletionSource.SetResult(args); 
    } 

    public TEventArgs WaitOnce() 
    { 
     TaskCompletionSource = new TaskCompletionSource<TEventArgs>(); 
     _subHandler.Invoke(Handler); 
     return TaskCompletionSource.Task.Result; 
    } 

    protected TaskCompletionSource<TEventArgs> TaskCompletionSource { get; set; } 

} 

用法:

EventArgs eventArgs = new EventWaiter<EventArgs>((h) => { button.Click += new EventHandler(h); }, (h) => { button.Click -= new EventHandler(h); }).WaitOnce();