35 votes

Le blocage et l'attente d'un événement

Il est parfois à bloquer mon fils en attendant qu'un événement se produise.

J'ai l'habitude de faire quelque chose comme ceci:

private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);

private void OnEvent(object sender, EventArgs e){
  _autoResetEvent.Set();
}

// ...
button.Click += OnEvent;
try{
  _autoResetEvent.WaitOne();
}
finally{
  button.Click -= OnEvent;
}

Cependant, il semble que ce devrait être quelque chose que j'ai pu extraire d'une classe commune (ou peut-être même quelque chose qui existe déjà dans le cadre).

Je voudrais être capable de faire quelque chose comme ceci:

EventWaiter ew = new EventWaiter(button.Click);
ew.WaitOne();
EventWaiter ew2 = new EventWaiter(form.Closing);
ew2.WaitOne();

Mais je ne peux pas vraiment trouver un moyen de construire une telle classe (je ne peux pas trouver un bon moyen valable pour transmettre l'événement comme un argument). Quelqu'un peut-il aider?

Pour donner un exemple de pourquoi cela peut être utile, pensez à quelque chose comme ceci:

var status = ShowStatusForm();
status.ShowInsertUsbStick();
bool cancelled = WaitForUsbStickOrCancel();
if(!cancelled){
  status.ShowWritingOnUsbStick();
  WriteOnUsbStick();
  status.AskUserToRemoveUsbStick();
  WaitForUsbStickToBeRemoved();
  status.ShowFinished();
}else{
  status.ShowCancelled();
}
status.WaitUntilUserPressesDone();

C'est beaucoup plus concis et lisible que l'équivalent du code écrit avec la logique réparties entre plusieurs méthodes. Mais pour mettre en œuvre WaitForUsbStickOrCancel(), WaitForUsbStickToBeRemoved et WaitUntilUserPressesDone() (à supposer que nous obtenons un événement lorsque les clés usb sont insérés ou retirés) j'ai besoin de ré-écrire "EventWaiter" à chaque fois. Bien sûr, vous devez être prudent de ne jamais exécuter sur le GUI-thread, mais parfois, c'est un bon compromis pour la simple code.

L'alternative serait ressembler à ceci:

var status = ShowStatusForm();
status.ShowInsertUsbStick();
usbHandler.Inserted += OnInserted;
status.Cancel += OnCancel;
//...
void OnInserted(/*..*/){
  usbHandler.Inserted -= OnInserted;
  status.ShowWritingOnUsbStick();
  MethodInvoker mi = () => WriteOnUsbStick();
  mi.BeginInvoke(WritingDone, null);
}
void WritingDone(/*..*/){
  /* EndInvoke */
  status.AskUserToRemoveUsbStick();
  usbHandler.Removed += OnRemoved;
}
void OnRemoved(/*..*/){
  usbHandler.Removed -= OnRemoved;
  status.ShowFinished();
  status.Done += OnDone;
}
/* etc */

Je trouve que beaucoup plus difficile à lire. Certes, il est loin d'être toujours que le flux sera donc linéaire, mais quand il l'est, j'aime le premier style.

Il est comparable à l'utilisation ShowMessage() et de la Forme.ShowDialog() - ils également bloquer jusqu'à ce que certains "cas" se produit (bien qu'elles fonctionnent un message en boucle si elles sont appelées sur le gui-thread).

3voto

Ricardo Villamil Points 3080

Ne passez pas le cas, passer un délégué qui correspond à la signature du gestionnaire d'événements. Cela semble effectivement hacky pour moi, donc avoir conscience de la possible mort de verrouillage de questions.

3voto

J'ai modifié Morts.Rabit classe EventWaiter à manipuler EventHandler<T>. De sorte que vous pouvez utiliser pour l'attente, tous les événements de type d' EventHandler<T>, cela signifie que votre délégué est quelque chose comme delegate void SomeDelegate(object sender, T EventsArgs).

 public class EventWaiter<T>
{

    private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
    private EventInfo _event = null;
    private object _eventContainer = null;

    public EventWaiter(object eventContainer, string eventName)
    {
        _eventContainer = eventContainer;
        _event = eventContainer.GetType().GetEvent(eventName);
    }

    public void WaitForEvent(TimeSpan timeout)
    {
        EventHandler<T> eventHandler = new EventHandler<T>((sender, args) => { _autoResetEvent.Set(); });
        _event.AddEventHandler(_eventContainer, eventHandler);
        _autoResetEvent.WaitOne(timeout);
        _event.RemoveEventHandler(_eventContainer, eventHandler);
    }
}

Et pour exemple, j'utilise que pour l'attente pour obtenir l'Url de HttpNotificationChannel quand je l'enregistrement pour windows le service de notification push.

            HttpNotificationChannel pushChannel = new HttpNotificationChannel(channelName);
            //ChannelUriUpdated is event 
            EventWaiter<NotificationChannelUriEventArgs> ew = new EventWaiter<NotificationChannelUriEventArgs>(pushChannel, "ChannelUriUpdated");
            pushChannel.Open();
            ew.WaitForEvent(TimeSpan.FromSeconds(30));

0voto

Dead.Rabit Points 817

Je l'ai précipité ensemble un exemple de travail en LinqPad à l'aide de la réflexion, de faire référence à la EventInfo objet avec une chaîne de caractères (veillez à ce que vous lâche moment de la compilation, la vérification). Le problème évident est qu'il n'existe aucune garantie qu'un événement ne sera jamais tiré, ou que l'événement de votre attend peut être déclenché avant l'EventWaiter classe est prête à commencer à bloquer donc je ne suis pas sûr que je serais sommeil confortable si je mets ça dans une production de l'app.

void Main()
{
    Console.WriteLine( "main thread started" );

    var workerClass = new WorkerClassWithEvent();
    workerClass.PerformWork();

    var waiter = new EventWaiter( workerClass, "WorkCompletedEvent" );
    waiter.WaitForEvent( TimeSpan.FromSeconds( 10 ) );

    Console.WriteLine( "main thread continues after waiting" );
}

public class WorkerClassWithEvent
{
    public void PerformWork()
    {
        var worker = new BackgroundWorker();
        worker.DoWork += ( s, e ) =>
        {
            Console.WriteLine( "threaded work started" );
            Thread.Sleep( 1000 ); // <= the work
            Console.WriteLine( "threaded work complete" );
        };
        worker.RunWorkerCompleted += ( s, e ) =>
        {
            FireWorkCompletedEvent();
            Console.WriteLine( "work complete event fired" );
        };

        worker.RunWorkerAsync();
    }

    public event Action WorkCompletedEvent;
    private void FireWorkCompletedEvent()
    {
        if ( WorkCompletedEvent != null ) WorkCompletedEvent();
    }
}

public class EventWaiter
{
    private AutoResetEvent _autoResetEvent = new AutoResetEvent( false );
    private EventInfo _event               = null;
    private object _eventContainer         = null;

    public EventWaiter( object eventContainer, string eventName )
    {
        _eventContainer = eventContainer;
        _event = eventContainer.GetType().GetEvent( eventName );
    }

    public void WaitForEvent( TimeSpan timeout )
    {
        _event.AddEventHandler( _eventContainer, (Action)delegate { _autoResetEvent.Set(); } );
        _autoResetEvent.WaitOne( timeout );
    }
}

Sortie

// main thread started
// threaded work started
// threaded work complete
// work complete event fired
// main thread continues after waiting

0voto

theres Points 184

Vous pouvez aussi essayer ceci:

class EventWaiter<TEventArgs> where TEventArgs : EventArgs
{
    private readonly Action<EventHandler<TEventArgs>> _unsubHandler;
    private readonly Action<EventHandler<TEventArgs>> _subHandler;

    public EventWaiter(Action<EventHandler<TEventArgs>> subHandler, Action<EventHandler<TEventArgs>> unsubHandler)
    {
        _unsubHandler = unsubHandler;
        _subHandler = subHandler;
    }

    protected void Handler(object sender, TEventArgs args)
    {
        _unsubHandler.Invoke(Handler);
        TaskCompletionSource.SetResult(args);
    }

    public  TEventArgs WaitOnce()
    {
        TaskCompletionSource = new TaskCompletionSource<TEventArgs>();
        _subHandler.Invoke(Handler);
        return TaskCompletionSource.Task.Result;
    }

    protected TaskCompletionSource<TEventArgs> TaskCompletionSource { get; set; } 

}

Utilisation:

EventArgs eventArgs = new EventWaiter<EventArgs>((h) => { button.Click += new EventHandler(h); }, (h) => { button.Click -= new EventHandler(h); }).WaitOnce();

-7voto

brutuscat Points 1912

Je pense que ces travaux, n'a pas essayé juste codé.

public class EventWaiter<T> where T : EventArgs
{
    private System.Threading.ManualResetEvent manualEvent;

    public EventWaiter(T e)
    {
        manualEvent = new System.Threading.ManualResetEvent(false);
        e += this.OnEvent;
    }

    public void OnEvent(object sender, EventArgs e)
    {
        manualEvent.Set();
    }

    public void WaitOne()
    {
        manualEvent.WaitOne();
    }

    public void Reset()
    {
        manualEvent.Reset();
    }
}

Ne pensais pas trop, mais ne peut pas comprendre comment le faire qu'il soit isolé de la EventArgs.

Jetez un oeil à la MSDN ManualResetEvent et vous découvrirez que vous pouvez sorte de chaîne et attend donc un truc bizarre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X