2009-01-29 32 views
35

Đôi khi, nó muốn chặn chuỗi của tôi trong khi chờ sự kiện xảy ra.Chặn và đang chờ sự kiện

Tôi thường làm điều đó một cái gì đó như thế này:

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; 
} 

Tuy nhiên, có vẻ như điều này nên được một cái gì đó mà tôi có thể giải nén vào một lớp học thông thường (hoặc thậm chí một cái gì đó đã tồn tại trong khuôn khổ).

Tôi muốn để có thể làm điều gì đó như thế này:

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

Nhưng tôi thực sự không thể tìm thấy một cách để xây dựng một lớp học như vậy (tôi không thể tìm thấy một cách hợp lệ tốt để vượt qua sự kiện như một đối số). Có ai giúp được không?

Để đưa ra một ví dụ về lý do tại sao điều này có thể có ích, hãy xem xét một cái gì đó như thế này:

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

Đây là nhiều hơn nữa súc tích và dễ đọc hơn các mã tương đương bằng văn bản với logic trải ra giữa nhiều phương pháp. Nhưng để thực hiện WaitForUsbStickOrCancel(), WaitForUsbStickToBeRemoved và WaitUntilUserPressesDone() (giả sử rằng chúng ta nhận được một sự kiện khi các thẻ usb được chèn vào hoặc bị loại bỏ), tôi cần phải thực hiện lại "EventWaiter" mỗi lần. Tất nhiên bạn phải cẩn thận để không bao giờ chạy điều này trên GUI-thread, nhưng đôi khi đó là một sự cân bằng đáng giá đối với mã đơn giản hơn.

Việc thay thế sẽ giống như thế này:

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 */ 

Tôi thấy rằng khó khăn hơn nhiều để đọc. Phải thừa nhận rằng, nó luôn luôn là dòng chảy sẽ được như vậy tuyến tính, nhưng khi nó được, tôi thích phong cách đầu tiên.

Có thể so sánh với việc sử dụng ShowMessage() và Form.ShowDialog() - chúng cũng chặn cho đến khi một số sự kiện xảy ra (mặc dù chúng sẽ chạy vòng lặp tin nhắn nếu chúng được gọi trên chuỗi gui).

+0

tôi tò mò tại sao chính xác bạn muốn làm điều này ... có thể giúp bạn xây dựng? –

+0

Tôi vẫn không hiểu tại sao bạn muốn chặn chuỗi thay vì chỉ chờ đợi sự kiện - điều đó thực hiện điều gì? –

+0

Bạn đã từng sửa lỗi này chưa? Im có cùng một vấn đề. – Carlo

Trả lời

3

Không vượt qua sự kiện, vượt qua một đại biểu phù hợp với chữ ký xử lý sự kiện. Điều này thực sự âm thanh hacky với tôi, vì vậy hãy nhận biết các vấn đề khóa chết tiềm năng.

+0

Tôi không chắc chắn tôi hiểu.Bạn có nghĩa là vượt qua các đại biểu đăng ký và hủy đăng ký khỏi sự kiện (ví dụ: EventWaiter mới (eh => {button.Click + = eh;}, eh => {button.Click - = eh;})) Điều đó sẽ làm việc, tôi cho rằng, nhưng tôi muốn một cái gì đó đơn giản hơn –

-7

Tôi nghĩ như thế này sẽ hoạt động, không thử chỉ được mã hóa.

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(); 
    } 
} 

Không nghĩ quá nhiều, nhưng không thể tìm ra cách tách biệt khỏi EventArgs.

Hãy xem qua số MSDN ManualResetEvent và bạn sẽ khám phá ra rằng bạn có thể xếp chuỗi chờ đợi và một số thứ kỳ lạ.

+1

Nó không thực sự là một vấn đề với EventArgs.Nó sẽ không có vấn đề gì để làm cho nó chung chung liên quan đến EventArgs-type. có vẻ như không có cách nào để vượt qua một sự kiện để một phương pháp, để bạn có thể đính kèm một sự kiện-xử lý (nó không phải là EventArgs bạn đính kèm như trong ví dụ của bạn) –

+0

OMG !! 't biết những gì tôi đã suy nghĩ !: S – brutuscat

0

Tôi đã vội vã cùng nhau một mẫu làm việc trong LinqPad bằng cách sử dụng phản ánh, nhận được một tham chiếu đến đối tượng EventInfo với một chuỗi (hãy cẩn thận khi bạn lỏng thời gian biên dịch kiểm tra). Vấn đề rõ ràng là không có người bảo đảm sự kiện nào sẽ bị sa thải, hoặc sự kiện mà bạn mong đợi có thể bị sa thải trước khi lớp EventWaiter sẵn sàng bắt đầu chặn vì vậy tôi không chắc mình sẽ ngủ thoải mái nếu tôi đặt nó vào một ứng dụng sản xuất.

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); 
    } 
} 

Output

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

tôi sửa đổi Chết.Lớp Event của Rabit EventWaiter để xử lý EventHandler<T>. Vì vậy, bạn có thể sử dụng để chờ đợi tất cả các loại sự kiện của EventHandler<T>, điều đó có nghĩa là đại biểu của bạn là một cái gì đó như 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); 
    } 
} 

Và ví dụ tôi sử dụng để chờ nhận Url từ HttpNotificationChannel khi đăng ký dịch vụ thông báo đẩy cửa sổ.

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

Bạn cũng có thể thử điều này:

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; } 

} 

Cách sử dụng:

EventArgs eventArgs = new EventWaiter<EventArgs>((h) => { button.Click += new EventHandler(h); }, (h) => { button.Click -= new EventHandler(h); }).WaitOnce(); 
Các vấn đề liên quan