Es que a veces quiero bloquear mi hilo mientras se espera por un evento que se produzca.

Que suele hacer es algo como esto:

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

Sin embargo, parece que esto debe ser algo que podía extraer a una clase común (o tal vez incluso algo que ya existe en el marco).

Me gustaría ser capaz de hacer algo como esto:

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

Pero realmente no puedo encontrar una manera de construir una clase (no puedo encontrar una buena forma válida para pasar el evento como un argumento). Alguien puede ayudar?

Para dar un ejemplo de por qué esto puede ser útil, considere la posibilidad de que algo como esto:

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

Esto es mucho más conciso y fácil de leer que el equivalente de un código escrito con la lógica extendió entre muchos métodos. Pero para implementar WaitForUsbStickOrCancel(), WaitForUsbStickToBeRemoved y WaitUntilUserPressesDone() (suponga que tenemos un evento, cuando las memorias usb se inserta o extrae) necesito reimplementar «EventWaiter» cada vez. Por supuesto, usted tiene que tener cuidado de no ejecutar esto en la interfaz gráfica de usuario-hilo, pero a veces eso es un equilibrio merece la pena por el simple código.

La alternativa sería algo como esto:

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 */

Me parece que es mucho más difícil de leer. Es cierto que está lejos de ser siempre que el flujo será tan lineal, pero cuando es así, me gusta el primer estilo.

Es comparable al uso de ShowMessage() y la Forma.ShowDialog() – también bloquean hasta que algún «evento» se produce (a pesar de que se ejecute un mensaje de bucle si se les llama en la interfaz gráfica de usuario-hilo).

  • Tengo curiosidad, ¿por qué querrías hacer esto… ¿puede explicar?
  • Todavía no entiendo por qué te gustaría bloquear el hilo en lugar de simplemente esperar para el evento – lo que hace que lograr?
  • ¿Alguna vez has solucionar este problema? Estoy teniendo el mismo problema.
  • Lo sentimos, no hay. Yo creo que no es una buena solución.
  • Que mierda =(, gracias por la rápida respuesta!
  • A menudo me gustaría hacer esto (aunque en última instancia, evitar que en el 99% de los casos) para proporcionar un bloqueo de «Esperar» método para una llamada asincrónica. Me residuos de un hilo, pero evitar la reprogramación de toda mi API.

InformationsquelleAutor Rasmus Faber | 2009-01-29

5 Comentarios

  1. 3

    No pase el evento, pasar a un delegado que coincide con la firma del controlador de eventos. Esto realmente suena hacky a mí, así que ser consciente de los posibles muertos bloqueo de problemas.

    • Yo no estoy seguro de entender. ¿Te refieres a pasar a los delegados que se suscribe y se da de baja del evento (es decir. nueva EventWaiter( eh => { botón.Haga clic en += eh; }, eh => { botón.Haga clic en -= eh; } ) ) Que iba a funcionar, supongo, pero yo prefiero algo más simple.
  2. 3

    He modificado Muertos.Rabit la clase de EventWaiter para manejar EventHandler<T>. Así que usted puede utilizar para esperar todo tipo de eventos de EventHandler<T>, que significa que el delegado es algo así como 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);
        }
    }

    Y por ejemplo yo uso ese para de espera para obtener la Url de HttpNotificationChannel cuando me registro a windows el servicio de notificaciones push.

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

    Me he apresurado juntos una muestra de trabajo en LinqPad el uso de la reflexión, obtener una referencia a la EventInfo objeto con una cadena (tener cuidado de como se suelta el momento de la compilación de cheques). La cuestión obvia es que no hay ninguna garantía de un evento jamás va a ser despedido, o que el evento de que su espera puede ser despedido antes de la EventWaiter clase está listo para iniciar el bloqueo, así que no estoy seguro de que me duerma cómodo si puedo poner esto en una producción de la aplicación.

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

    Salida

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

    Usted también puede probar esto:

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

    Uso:

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

    Creo que como estos deberían de trabajo, no se trató de codificado.

    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();
    }
    }

    No pensado demasiado, pero no puedo averiguar cómo hacer que sea aislado de la EventArgs.

    Echar un vistazo a la MSDN ManualResetEvent y usted descubrirá que usted puede tipo de cadena de la espera y por lo que algunas cosas raras.

    • No es realmente un problema con el EventArgs. No sería ningún problema para hacerlo genérico con respecto a la EventArgs tipo. El problema es que no parece haber ninguna manera de pasar de un evento, un método, de modo que usted puede conectar un controlador de eventos (no es EventArgs adjuntar como en tu ejemplo).
    • OMG!! Sí tienes razón, no sé en qué estaba pensando! :S

Dejar respuesta

Please enter your comment!
Please enter your name here