the - raise event c#
Bloqueando y esperando un evento (5)
Creo que esto debería funcionar, no lo intenté simplemente 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 pensé demasiado, pero no puedo entender cómo aislarlo de los EventArgs.
Eche un vistazo al ManualResetEvent de MSDN y descubrirá que puede ordenar las esperas y algunas cosas raras.
A veces, quiero bloquear mi hilo mientras espero que ocurra un evento.
Por lo general, hago 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 debería ser algo que podría extraer a una clase común (o tal vez incluso algo que ya existe en el marco).
Me gustaría poder 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 así (no puedo encontrar una buena manera válida de pasar el evento como argumento). ¿Alguien puede ayudar?
Para dar un ejemplo de por qué esto puede ser útil, considere 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 legible que el código equivalente escrito con la lógica repartida entre muchos métodos. Pero para implementar WaitForUsbStickOrCancel (), WaitForUsbStickToBeRemoved y WaitUntilUserPressesDone () (supongamos que obtenemos un evento cuando se insertan o eliminan los controles USB), necesito volver a implementar "EventWaiter" cada vez. Por supuesto, debe tener cuidado de nunca ejecutar esto en el hilo de la interfaz gráfica de usuario, pero a veces es una solución que vale la pena para el código más simple.
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 */
Encuentro que es mucho más difícil de leer. Es cierto que está lejos de ser siempre que el flujo sea tan lineal, pero cuando lo es, me gusta el primer estilo.
Es comparable al uso de ShowMessage () y Form.ShowDialog (): también se bloquean hasta que se produzca algún "evento" (aunque ejecutarán un bucle de mensaje si se invocan en la secuencia de comandos).
Me apresuré a juntar una muestra de trabajo en LinqPad usando reflexión, obteniendo una referencia al objeto EventInfo con una cadena (tenga cuidado ya que pierde la verificación del tiempo de compilación). El problema obvio es que no hay garantía de que se dispare un evento, o que el evento que esperas pueda ser despedido antes de que la clase de EventWaiter esté lista para empezar a bloquear, así que no estoy seguro si dormiré cómodamente si coloco esto en una aplicación de producció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
Modifiqué la clase EventWaiter de Dead.Rabit para manejar EventHandler<T>
. Entonces puede usar para esperar todos los eventos tipo de EventHandler<T>
, eso significa que su 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, uso eso para esperar a obtener Url desde HttpNotificationChannel cuando me registre en el servicio de notificación push de Windows.
HttpNotificationChannel pushChannel = new HttpNotificationChannel(channelName);
//ChannelUriUpdated is event
EventWaiter<NotificationChannelUriEventArgs> ew = new EventWaiter<NotificationChannelUriEventArgs>(pushChannel, "ChannelUriUpdated");
pushChannel.Open();
ew.WaitForEvent(TimeSpan.FromSeconds(30));
No pase el evento, pase un delegado que coincida con la firma del controlador de eventos. En realidad, esto suena raro para mí, así que ten en cuenta los posibles problemas de bloqueo.
También puedes 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();