2010-05-12 101 views
8

我有一個Sender類發送MessageIChannel如何等待C#事件被提出?

public class MessageEventArgs : EventArgs { 
    public Message Message { get; private set; } 
    public MessageEventArgs(Message m) { Message = m; } 
} 

public interface IChannel { 
    public event EventHandler<MessageEventArgs> MessageReceived; 
    void Send(Message m); 
} 

public class Sender { 
    public const int MaxWaitInMs = 5000; 
    private IChannel _c = ...; 

    public Message Send(Message m) { 
    _c.Send(m); 
    // wait for MaxWaitInMs to get an event from _c.MessageReceived 
    // return the message or null if no message was received in response 
    } 
} 

當我們發送信息,IChannel有時會響應這取決於什麼樣的Message是通過提高MessageReceived事件發送。事件參數包含感興趣的消息。

我想Sender.Send()方法來等待一小段時間,看看是否引發此事件。如果是這樣,我將返回其MessageEventArgs.Message財產。如果不是,我會返回一個空值Message

我該如何等待這種方式?我寧願不要使用ManualResetEvents等線程,因此堅持常規的event對我來說是最合適的。

+0

」。 ..不希望做線程legwork ...「 - 這應該解釋爲你的應用程序完全在一個線程內運行? – 2010-05-12 16:42:30

+0

不,使用工作線程並且很好,它們在應用程序中的其他地方使用(例如,IChannel實現會產生一個新線程來寫入流)。儘管如此,我仍想繼續使用委託和事件的語法糖,而不是使用較低級別的System.Threading。 – 2010-05-12 16:44:19

+3

代表和事件不是線程的語法糖。事件在同一線程中引發並處理(它是函數調用中的語法糖,其工作方式相同) – 2010-05-12 16:47:13

回答

14

使用AutoResetEvent

給我幾分鐘,我會扔在一起的樣本。

這就是:

public class Sender 
{ 
    public static readonly TimeSpan MaxWait = TimeSpan.FromMilliseconds(5000); 

    private IChannel _c; 
    private AutoResetEvent _messageReceived; 

    public Sender() 
    { 
     // initialize _c 
     this._messageReceived = new AutoResetEvent(false); 
     this._c.MessageReceived += this.MessageReceived; 
    } 

    public Message Send(Message m) 
    { 
     this._c.Send(m); 
     // wait for MaxWaitInMs to get an event from _c.MessageReceived 
     // return the message or null if no message was received in response 


     // This will wait for up to 5000 ms, then throw an exception. 
     this._messageReceived.WaitOne(MaxWait); 

     return null; 
    } 

    public void MessageReceived(object sender, MessageEventArgs e) 
    { 
     //Do whatever you need to do with the message 

     this._messageReceived.Set(); 
    } 
} 
+0

我知道我可以用ManualResetEvent來做到這一點,但就像我說過的(「我寧願不用'ManualResetEvents'做線程修腳」),我希望有另一種選擇。 – 2010-05-12 16:59:07

+0

對不起,我錯過了你原來的帖子。 即使您創建委託並調用'BeginInvoke',您仍然必須有回調方法,並且您仍然必須以某種方式手動同步這些線程。 雖然,IAsyncResult實例應該爲您提供一個WaitHandle,這可以簡化一些事情,但不是很多。 – Toby 2010-05-12 17:18:16

+0

這看起來好像能做到這一點,而且我認爲這很簡單。謝謝! – 2010-05-12 17:29:32

1

您是否嘗試過將功能分配到異步調用的委託,然後調用mydelegateinstance.BeginInvoke?

Linky for reference.

隨着下面的例子中,只需要調用

FillDataSet(ref table, ref dataset); 

,它會用魔法,好像工作。 :)

#region DataSet manipulation 
///<summary>Fills a the distance table of a dataset</summary> 
private void FillDataSet(ref DistanceDataTableAdapter taD, ref MyDataSet ds) { 
    using (var myMRE = new ManualResetEventSlim(false)) { 
    ds.EnforceConstraints = false; 
    ds.Distance.BeginLoadData(); 
    Func<DistanceDataTable, int> distanceFill = taD.Fill; 
    distanceFill.BeginInvoke(ds.Distance, FillCallback<DistanceDataTable>, new object[] { distanceFill, myMRE }); 
    WaitHandle.WaitAll(new []{ myMRE.WaitHandle }); 
    ds.Distance.EndLoadData(); 
    ds.EnforceConstraints = true; 
    } 
} 
/// <summary> 
/// Callback used when filling a table asynchronously. 
/// </summary> 
/// <param name="result">Represents the status of the asynchronous operation.</param> 
private void FillCallback<MyDataTable>(IAsyncResult result) where MyDataTable: DataTable { 
    var state = result.AsyncState as object[]; 
    Debug.Assert((state != null) && (state.Length == 2), "State variable is either null or an invalid number of parameters were passed."); 

    var fillFunc = state[0] as Func<MyDataTable, int>; 
    var mre = state[1] as ManualResetEventSlim; 
    Debug.Assert((mre != null) && (fillFunc != null)); 
    int rowsAffected = fillFunc.EndInvoke(result); 
    Debug.WriteLine(" Rows: " + rowsAffected.ToString()); 
    mre.Set(); 
} 
+0

+1:良好的呼叫,IMO。基本上爲你實現'ManualResetEvent'。 – IAbstract 2010-05-12 16:46:41

+1

我不知道我明白這個答案。你能說明這個例子中你的意思嗎? – 2010-05-12 16:47:24

+0

我會用我自己的代碼(它完美地工作)的例子更新它2秒.. – Geoff 2010-05-12 16:52:35

0

也許你的messageReceived方法應該簡單地標誌,值到您的IChannel接口的屬性,而執行INotifyPropertyChanged的事件處理程序,這樣,當更改屬性,你會被告知。

通過這樣做,你的發件人類可以循環播放,直到最大等待時間已經過去時,或當PropertyChanged事件處理程序時,成功地打破了循環。如果你的循環沒有被破壞,那麼該消息應被視爲從未收到。

+0

這會起作用,但它需要更改'IChannel'合約,這看起來不太理想。從設計的角度來看,我認爲IChannel不應該改變,因爲它的實現者在使用它的方式上沒有任何變化。 – 2010-05-12 16:57:25

-1

WaitOne是真正完成這個工作的工具。簡而言之,您希望在0到MaxWaitInMs毫秒之間等待作業完成。你真的有兩個選擇,輪詢完成或同步線程與一些可以等待任意時間的構造。

既然你也知道要做到這一點的正確方式,爲後人我會後輪詢版本:

MessageEventArgs msgArgs = null; 
var callback = (object o, MessageEventArgs args) => { 
    msgArgs = args; 
}; 

_c.MessageReceived += callback; 
_c.Send(m); 

int msLeft = MaxWaitInMs; 
while (msgArgs == null || msLeft >= 0) { 
    Thread.Sleep(100); 
    msLeft -= 100; // you should measure this instead with say, Stopwatch 
} 

_c.MessageRecieved -= callback; 
0

有用的樣品與的AutoResetEvent

using System; 
    using System.Threading; 

    class WaitOne 
    { 
     static AutoResetEvent autoEvent = new AutoResetEvent(false); 

     static void Main() 
     { 
      Console.WriteLine("Main starting."); 

      ThreadPool.QueueUserWorkItem(
       new WaitCallback(WorkMethod), autoEvent); 

      // Wait for work method to signal. 
      autoEvent.WaitOne(); 
      Console.WriteLine("Work method signaled.\nMain ending."); 
     } 

     static void WorkMethod(object stateInfo) 
     { 
      Console.WriteLine("Work starting."); 

      // Simulate time spent working. 
      Thread.Sleep(new Random().Next(100, 2000)); 

      // Signal that work is finished. 
      Console.WriteLine("Work ending."); 
      ((AutoResetEvent)stateInfo).Set(); 
     } 
    }