2010-05-12 9 views
8

tengo una clase que envía una SenderMessage en un IChannel:¿Cómo espero a que se produzca un evento de 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 
    } 
} 

Cuando enviamos mensajes, el IChannel veces da una respuesta dependiendo de qué tipo de Message fue enviado por provocar el evento MessageReceived. Los argumentos del evento contienen el mensaje de interés.

Quiero que el método Sender.Send() espere un tiempo breve para ver si se produce este evento. Si es así, devolveré su propiedad MessageEventArgs.Message. Si no, devuelvo un nulo Message.

¿Cómo puedo esperar de esta manera? Preferiría no tener que hacer el trabajo de subprocesamiento con ManualResetEvents y tal, por lo que apegarse a los event s normales sería óptimo para mí.

+0

". ..preferir no tener que hacer el trabajo de subprocesos ... "- ¿Debería interpretarse esto como su aplicación se ejecuta completamente en un solo hilo? –

+0

No, usar subprocesos de trabajo y eso está bien, y se usan en otra parte de la aplicación (por ejemplo, la implementación de IChannel genera un nuevo hilo para escribir en una secuencia). Sin embargo, quiero quedarme con el azúcar sintáctico de delegados y eventos, y no usar el 'System.Threading' de bajo nivel. –

+3

delegados y eventos no son azúcar sintáctica para enhebrar. Los eventos se plantean y manejan en el mismo hilo (es azúcar sintáctica sobre una llamada a función y funciona de la misma manera) –

Respuesta

14

Utilice un AutoResetEvent.

Dame unos minutos y armaré una muestra.

Aquí está:

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

Sé que puedo hacer esto con un ManualResetEvent, pero como dije ("Preferiría no tener que hacer el trabajo de subprocesamiento con' ManualResetEvents' "), espero que haya otra opción. –

+0

Lo siento, me perdí eso en tu publicación original. Incluso si crea un delegado y llama a 'BeginInvoke', aún debe tener un método de devolución de llamada, y de todos modos tendrá que sincronizar manualmente los hilos. Aunque, la instancia IAsyncResult debe proporcionar un WaitHandle para usted, lo que simplificaría las cosas un poco, pero no demasiado. – Toby

+0

Esto parece que hará el truco, y es lo más sencillo que puedo conseguir, supongo. ¡Gracias! –

1

¿Ha intentado asignar la función para llamar asincrónicamente a un delegado, y luego invocar el mydelegateinstance.BeginInvoke?

Linky for reference.

Con el siguiente ejemplo, sólo llame

FillDataSet(ref table, ref dataset); 

y que funcionará como por 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: buena llamada, IMO. Básicamente implementa 'ManualResetEvent' para usted. – IAbstract

+1

No estoy seguro de entender esta respuesta. ¿Puedes ilustrar lo que quieres decir en el contexto de este ejemplo? –

+0

Lo actualizaré con un ejemplo de mi propio código (que funciona sin problemas) 2 segundos .. – Geoff

0

Tal vez su método MessageReceived simplemente debe marcar un valor a una propiedad de su interfaz ICHANNEL, mientras se implementa el controlador de eventos INotifyPropertyChanged, por lo que sería aconsejable cuando se cambia la propiedad .

Al hacerlo, su clase Sender podría realizar un bucle hasta que transcurra el tiempo máximo de espera, o cada vez que se produzca el controlador de eventos PropertyChanged, rompiendo el ciclo con éxito. Si su bucle no se rompe, entonces el mensaje se considerará como nunca recibido.

+0

Eso funcionaría, pero requeriría cambiar el contrato 'IChannel', que parece menos que deseable. Desde el punto de vista del diseño, no creo que IChannel deba cambiar, dado que nada ha cambiado aquí sobre la forma en que los implementadores lo están utilizando. –

-1

WaitOne es realmente la herramienta adecuada para este trabajo. En resumen, desea esperar entre 0 y MaxWaitInMs milisegundos para completar un trabajo. Realmente tiene dos opciones, sondee la terminación o sincronice los hilos con alguna construcción que pueda esperar una cantidad de tiempo arbitraria.

Puesto que usted es muy consciente de la manera correcta de hacer esto, para la posteridad Voy a publicar la versión de votación:

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

muestra útil 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(); 
     } 
    } 
Cuestiones relacionadas