c# - net - ¿Cómo puedo hacer que un controlador de eventos se ejecute de forma asincrónica?
manejo de eventos en c# (7)
Además, si no quiero pasar ningún parámetro al controlador de eventos mi sintaxis es correcta usando OperationFinished (null, new EventArgs ())?
No. Normalmente, lo llamarías así:
OperationFinished(this, EventArgs.Empty);
Siempre debe pasar un objeto como remitente: se espera en el patrón (aunque normalmente se ignora). EventArgs.Empty es mejor que los nuevos EventArgs (), también.
Para disparar esto en un subproceso separado, la opción más fácil es, probablemente, utilizar el grupo de subprocesos:
private void RaiseOperationFinished()
{
ThreadPool.QueueUserWorkItem( new WaitCallback( (s) =>
{
if (this.OperationFinished != null)
this.OperationFinished(this, EventArgs.Empty);
}));
}
Dicho esto, plantear un evento en un hilo separado es algo que debe documentarse exhaustivamente, ya que podría causar un comportamiento inesperado.
Estoy escribiendo un programa de Visual C # que ejecuta un bucle continuo de operaciones en un hilo secundario. Ocasionalmente, cuando ese hilo termina una tarea, quiero que desencadene un controlador de eventos. Mi programa hace eso, pero cuando se activa el controlador de eventos, el subproceso secundario espera hasta que el controlador de eventos finalice antes de continuar el hilo. ¿Cómo lo hago continuar? Esta es la forma en que actualmente lo tengo estructurado ...
class TestClass
{
private Thread SecondaryThread;
public event EventHandler OperationFinished;
public void StartMethod()
{
...
SecondaryThread.Start(); //start the secondary thread
}
private void SecondaryThreadMethod()
{
...
OperationFinished(null, new EventArgs());
... //This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
}
}
Este código es parte de una API para uno de mis dispositivos. Cuando se desencadena el evento OperationFinished, quiero que la aplicación cliente pueda hacer lo que necesite (es decir, actualizar la GUI según corresponda) sin recurrir a la operación de la API.
Además, si no quiero pasar ningún parámetro al controlador de eventos mi sintaxis es correcta usando OperationFinished(null, new EventArgs())
?
Con la Biblioteca de tareas paralelas , ahora es posible hacer lo siguiente:
Task.Factory.FromAsync( ( asyncCallback, @object ) => this.OperationFinished.BeginInvoke( this, EventArgs.Empty, asyncCallback, @object ), this.OperationFinished.EndInvoke, null );
Entonces, ¿desea plantear el evento de una manera que evite que los oyentes bloqueen el hilo de fondo? Dame un par de minutos para dar un ejemplo; es bastante simple :-)
Aquí vamos: primero una nota importante! Cada vez que llame a BeginInvoke
debe llamar a la correspondiente EndInvoke
; de lo contrario, si el método invocado arrojó una excepción o devolvió un valor, el hilo de ThreadPool nunca se devolverá al grupo, lo que provocará una fuga de hilo.
class TestHarness
{
static void Main(string[] args)
{
var raiser = new SomeClass();
// Emulate some event listeners
raiser.SomeEvent += (sender, e) => { Console.WriteLine(" Received event"); };
raiser.SomeEvent += (sender, e) =>
{
// Bad listener!
Console.WriteLine(" Blocking event");
System.Threading.Thread.Sleep(5000);
Console.WriteLine(" Finished blocking event");
};
// Listener who throws an exception
raiser.SomeEvent += (sender, e) =>
{
Console.WriteLine(" Received event, time to die!");
throw new Exception();
};
// Raise the event, see the effects
raiser.DoSomething();
Console.ReadLine();
}
}
class SomeClass
{
public event EventHandler SomeEvent;
public void DoSomething()
{
OnSomeEvent();
}
private void OnSomeEvent()
{
if (SomeEvent != null)
{
var eventListeners = SomeEvent.GetInvocationList();
Console.WriteLine("Raising Event");
for (int index = 0; index < eventListeners.Count(); index++)
{
var methodToInvoke = (EventHandler)eventListeners[index];
methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, null);
}
Console.WriteLine("Done Raising Event");
}
}
private void EndAsyncEvent(IAsyncResult iar)
{
var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
var invokedMethod = (EventHandler)ar.AsyncDelegate;
try
{
invokedMethod.EndInvoke(iar);
}
catch
{
// Handle any exceptions that were thrown by the invoked method
Console.WriteLine("An event listener went kaboom!");
}
}
}
Mira la clase BackgroundWorker . Creo que hace exactamente lo que estás pidiendo.
EDITAR: Lo que creo que estás preguntando es cómo disparar un evento cuando solo se ha completado una pequeña parte de la tarea de fondo general. BackgroundWorker proporciona un evento llamado "ProgressChanged" que le permite informar al hilo principal que se ha completado una parte del proceso en general. Luego, cuando se completa todo el trabajo de sincronización, se genera el evento "RunWorkerCompleted".
Prefiero definir un método que pase al hilo secundario como un delegado que actualiza la UI. Primero defina un delegado:
public delegate void ChildCallBackDelegate();
En el hilo hijo, defina un miembro delegado:
public ChildCallbackDelegate ChildCallback {get; set;}
En la clase de llamada, defina el método que actualiza la UI. Tendrá que envolverlo en el despachador del control objetivo ya que se llama desde un hilo separado. Tenga en cuenta el BeginInvoke. En este contexto, no se requiere EndInvoke:
private void ChildThreadUpdater()
{
yourControl.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background
, new System.Threading.ThreadStart(delegate
{
// update your control here
}
));
}
Antes de iniciar su subproceso secundario, establezca su propiedad ChildCallBack:
theChild.ChildCallBack = new ChildCallbackDelegate(ChildThreadUpdater);
Luego, cuando el hilo hijo quiere actualizar el padre:
ChildCallBack();
Pruebe los métodos BeginInvoke y EndInvoke en el delegado del evento; estos regresan inmediatamente y le permiten usar sondeo, un identificador de espera o una función de devolución de llamada para notificarlo cuando el método se haya completado. Vea here para una descripción general; en tu ejemplo, el evento es el delegado que usarás
Tal vez Method2 o Method3 a continuación pueden ayudar :)
public partial class Form1 : Form
{
private Thread SecondaryThread;
public Form1()
{
InitializeComponent();
OperationFinished += callback1;
OperationFinished += callback2;
OperationFinished += callback3;
}
private void Form1_Load(object sender, EventArgs e)
{
SecondaryThread = new Thread(new ThreadStart(SecondaryThreadMethod));
SecondaryThread.Start();
}
private void SecondaryThreadMethod()
{
Stopwatch sw = new Stopwatch();
sw.Restart();
OnOperationFinished(new MessageEventArg("test1"));
OnOperationFinished(new MessageEventArg("test2"));
OnOperationFinished(new MessageEventArg("test3"));
//This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
sw.Stop();
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += "Time taken (ms): " + sw.ElapsedMilliseconds + "/n";
});
}
void callback1(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "/n";
});
}
void callback2(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "/n";
});
}
void callback3(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "/n";
});
}
public event EventHandler<MessageEventArg> OperationFinished;
protected void OnOperationFinished(MessageEventArg e)
{
//##### Method1 - Event raised on the same thread #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// handler(this, e);
//}
//##### Method2 - Event raised on (the same) separate thread for all listener #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// Task.Factory.StartNew(() => handler(this, e));
//}
//##### Method3 - Event raised on different threads for each listener #####
if (OperationFinished != null)
{
foreach (EventHandler<MessageEventArg> handler in OperationFinished.GetInvocationList())
{
Task.Factory.FromAsync((asyncCallback, @object) => handler.BeginInvoke(this, e, asyncCallback, @object), handler.EndInvoke, null);
}
}
}
}
public class MessageEventArg : EventArgs
{
public string Message { get; set; }
public MessageEventArg(string message)
{
this.Message = message;
}
}
}