2010-05-12 99 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(); 
     } 
    }