2010-05-12 11 views
8

Ho una classe Sender che invia una Message su un IChannel:Come posso aspettare che venga sollevato un evento 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 
    } 
} 

Quando inviamo messaggi, il IChannel a volte dà una risposta a seconda del tipo di Message è stato inviato aumentando l'evento MessageReceived. Gli argomenti dell'evento contengono il messaggio di interesse.

voglio Sender.Send() metodo di aspettare per un breve periodo di tempo per vedere se questo evento viene generato. In tal caso, restituirò la proprietà MessageEventArgs.Message. In caso contrario, restituisco un valore nullo Message.

Come posso aspettare in questo modo? Preferirei non avere il legwork di threading con ManualResetEvents e così, quindi attenersi al normale event s sarebbe ottimale per me.

+0

". ..prefer non devi fare il legwork di threading ... "- Questo dovrebbe essere interpretato come la tua app viene eseguita interamente all'interno di un singolo thread? –

+0

No, l'utilizzo di thread di lavoro e simili va bene, e vengono utilizzati altrove nell'app (ad esempio, l'implementazione di IChannel genera un nuovo thread per scrivere in un flusso). Tuttavia, voglio rimanere con lo zucchero sintattico dei delegati e degli eventi e non utilizzare il 'System.Threading 'di livello inferiore. –

+3

i delegati e gli eventi non sono zucchero sintattico per il threading. Gli eventi vengono generati e gestiti sullo stesso thread (è lo zucchero sintattico durante una chiamata di funzione e funziona allo stesso modo) –

risposta

14

Utilizzare un AutoResetEvent.

Dammi qualche minuto e mi butto insieme un campione.

Eccolo:

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

So che posso farlo con un ManualResetEvent, ma come ho detto ("Preferirei non avere il legwork di threading con' ManualResetEvents' "), spero che ci sia un'altra opzione. –

+0

Mi dispiace, l'ho perso nel tuo post originale. Anche se si crea un delegato e si chiama 'BeginInvoke', è ancora necessario disporre di un metodo di richiamata e sarà comunque necessario sincronizzare manualmente i thread in qualche modo. Anche se l'istanza IAsyncResult dovrebbe fornire un WaitHandle per te, il che semplificherebbe le cose un po ', ma non molto. – Toby

+0

Questo sembra che farà il trucco, ed è più semplice che posso ottenere, suppongo. Grazie! –

1

Hai provato assegnando la funzione di chiamare in modo asincrono a un delegato, poi invocando il mydelegateinstance.BeginInvoke?

Linky for reference.

Con l'esempio qui sotto, basta chiamare

FillDataSet(ref table, ref dataset); 

e che funzionerà come per magia. :)

#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: buona chiamata, IMO. Fondamentalmente implementa 'ManualResetEvent' per te. – IAbstract

+1

Non sono sicuro di aver capito questa risposta. Puoi illustrare cosa intendi nel contesto di questo esempio? –

+0

Lo aggiorno con un esempio dal mio codice (che funziona perfettamente) 2 secondi .. – Geoff

0

Forse il vostro metodo di MessageReceived semplicemente dovrebbe contrassegnare un valore a una proprietà dell'interfaccia IChannel, mentre l'attuazione del gestore di eventi INotifyPropertyChanged, in modo che sarebbe consigliato quando la proprietà è cambiata .

In questo modo, la classe Sender potrebbe eseguire il loop fino al termine del tempo di attesa massimo o ogni volta che si verifica il gestore di eventi PropertyChanged, interrompendone il ciclo. Se il tuo loop non si rompe, allora il messaggio sarà considerato come mai ricevuto.

+0

Funzionerebbe, ma richiederebbe la modifica del contratto 'IChannel', che sembra meno desiderabile. Dal punto di vista del design, non credo che IChannel debba cambiare, dato che non è cambiato nulla riguardo al modo in cui i suoi implementatori lo stanno utilizzando. –

-1

WaitOne è davvero lo strumento giusto per questo lavoro. In breve, si desidera attendere tra 0 e MaxWaitInMs millisecondi per completare un lavoro. Hai davvero due opzioni, esegui il polling per il completamento o sincronizza i thread con qualche costrutto che può aspettare una quantità arbitraria di tempo.

Visto che siete ben consapevoli del modo giusto per fare questo, per i posteri vi posterò la versione di polling:

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

campione utile con 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(); 
     } 
    } 
Problemi correlati