2010-05-12 4 views
8

J'ai une classe Sender qui envoie un Message sur un IChannel:Comment puis-je attendre qu'un événement C# soit déclenché?

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

Lorsque nous envoyons des messages, le IChannel donne parfois une réponse en fonction de ce type de Message a été envoyé par l'événement est MessageReceived. Les arguments d'événement contiennent le message d'intérêt.

Je souhaite que la méthode Sender.Send() attende un court instant pour voir si cet événement est déclenché. Si c'est le cas, je vais retourner sa propriété MessageEventArgs.Message. Sinon, je renvoie un Message nul.

Comment puis-je attendre de cette façon? Je préférerais ne pas avoir faire le filetage avec ManualResetEvents et ainsi de suite, donc coller à event s serait optimal pour moi.

+0

». .. préférez ne pas avoir à faire le threading legwork ... "- Cela devrait-il être interprété comme votre application fonctionne entièrement dans un seul thread? –

+0

Non, l'utilisation de threads de travail et autres est bonne, et ils sont utilisés ailleurs dans l'application (par exemple, l'implémentation IChannel génère un nouveau thread pour écrire dans un flux). Je veux rester avec le sucre syntaxique des délégués et des événements, cependant, et ne pas utiliser le 'System.Threading' de niveau inférieur. –

+3

Les délégués et les événements ne sont pas du sucre syntaxique pour le threading. Les événements sont levés et traités sur le même thread (c'est du sucre syntaxique sur un appel de fonction et fonctionne de la même manière) –

Répondre

14

Utilisez un AutoResetEvent. Donnez-moi quelques minutes et je vais jeter ensemble un échantillon.

Ici, il 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

Je sais que je peux le faire avec un ManualResetEvent, mais comme je l'ai dit ("Je préférerais ne pas avoir à faire le threading avec' ManualResetEvents' "), j'espère qu'il y a une autre option. –

+0

Désolé, j'ai manqué cela dans votre message original. Même si vous créez un délégué et appelez 'BeginInvoke', vous devez toujours avoir une méthode de rappel, et vous devrez toujours synchroniser manuellement les threads d'une manière ou d'une autre. Bien que, l'instance IAsyncResult devrait fournir un WaitHandle pour vous, ce qui simplifierait un peu les choses, mais pas beaucoup. – Toby

+0

On dirait que ça va faire l'affaire, et c'est aussi simple que possible, je suppose. Merci! –

1

Avez-vous essayé d'affecter la fonction pour appeler de manière asynchrone à un délégué, puis d'appeler l'instance mydelegateinstance.BeginInvoke?

Linky for reference.

Avec l'exemple ci-dessous, il suffit d'appeler

FillDataSet(ref table, ref dataset); 

et ça va marcher comme par magie. :)

#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: bon appel, IMO. Implémente fondamentalement 'ManualResetEvent' pour vous. – IAbstract

+1

Je ne suis pas sûr de comprendre cette réponse. Pouvez-vous illustrer ce que vous voulez dire dans le contexte de cet exemple? –

+0

Je vais mettre à jour avec un exemple de mon propre code (qui fonctionne parfaitement) 2 secondes .. – Geoff

0

Peut-être votre méthode MessageReceived devrait simplement marquer une valeur à une propriété de votre interface IChannel, en mettant en œuvre le gestionnaire d'événements INotifyPropertyChanged, de sorte que vous seriez avisé lorsque la propriété change . Ce faisant, votre classe d'expéditeur peut faire une boucle jusqu'à ce que le temps d'attente maximum soit écoulé ou à chaque fois que le gestionnaire d'événements PropertyChanged se produit, la boucle est rompue avec succès. Si votre boucle ne se casse pas, le message doit être considéré comme jamais reçu.

+0

Cela fonctionnerait, mais cela nécessiterait de changer le contrat 'IChannel', ce qui semble moins que souhaitable. Du point de vue de la conception, je ne pense pas que IChannel devrait changer car rien dans la manière dont ses implémenteurs l'utilisent n'a changé ici. –

-1

WaitOne est vraiment le bon outil pour ce travail. En bref, vous souhaitez attendre entre 0 et MaxWaitInMs millisecondes pour qu'un travail soit terminé. Vous avez vraiment deux choix, interroger pour compléter ou synchroniser les threads avec une construction qui peut attendre une quantité arbitraire de temps.

Puisque vous êtes bien au courant de la bonne façon de le faire, pour la postérité, je posterai la version de vote:

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

échantillon utile avec 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(); 
     } 
    } 
Questions connexes