.NET: ¿Cómo se tiene disponible la información del hilo principal de la señal del hilo de fondo?


10

¿Cuál es la técnica adecuada para tener ThreadA señal ThreadB de algún evento, sin tener ThreadB sentarse bloqueado a la espera de un evento suceda?

tengo un hilo de fondo que estará llenando una lista compartida <T>. Estoy tratando de encontrar una manera de señalar asincrónicamente el hilo "principal" que hay datos disponibles para ser recogidos.


i considerado establecer un evento con un objeto EventWaitHandle, pero no puedo tener mi hilo conductor sentado en un Event.WaitOne().


i se considera que poseen una devolución de llamada delegado, pero a) No quiero que el hilo principal haciendo un trabajo en el delegado: el hilo tiene que volver al trabajo añadiendo más cosas - no quiero que esperando mientras el delegado se ejecuta, y b) el delegado necesita ser ordenado en el hilo principal, pero no estoy ejecutando una UI, no tengo Control para invocar al delegado.


i considerados tienen una devolución de llamada delegado que simplemente inicia una System.Windows.Forms.Timer intervalo de cero (con acceso hilo a la sincronizada temporizador). De esta manera el hilo sólo tiene que ser pegado como residencia

Timer.Enabled = true;

pero que parece como un truco.

En el pasado, mi objeto habría creado una ventana oculta e hizo que los mensajes de la publicación del hilo estuvieran ocultos en el HWND de Windows. Consideré crear un control oculto, pero deduzco que no se puede. Invocar en un control sin un identificador creado. Además, no tengo UI: mi objeto podría haberse creado en un servidor web, servicio o consola, no quiero que aparezca un control gráfico, ni quiero compilar una dependencia en System.Windows. Formularios


consideré que mi objeto exponer una interfaz ISynchronizeInvoke, pero entonces habría que poner en práctica .Invoke(), y ese es mi problema.


¿Cuál es la técnica adecuada para tener enhebrar un hilo de señal B de algún evento, sin tener hilo B sentarse bloqueado a la espera de un evento suceda?

10

Aquí hay un ejemplo de código para la clase System.ComponentModel.BackgroundWorker.

private static BackgroundWorker worker = new BackgroundWorker(); 
    static void Main(string[] args) 
    { 
     worker.DoWork += worker_DoWork; 
     worker.RunWorkerCompleted += worker_RunWorkerCompleted; 
     worker.ProgressChanged += worker_ProgressChanged; 
     worker.WorkerReportsProgress = true; 

     Console.WriteLine("Starting application."); 
     worker.RunWorkerAsync(); 

     Console.ReadKey(); 
    } 

    static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     Console.WriteLine("Progress."); 
    } 

    static void worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     Console.WriteLine("Starting doing some work now."); 

     for (int i = 0; i < 5; i++) 
     { 
      Thread.Sleep(1000); 
      worker.ReportProgress(i); 
     } 
    } 

    static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     Console.WriteLine("Done now."); 
    } 

1

Si utiliza un backgroundworker para iniciar el segundo hilo y utiliza el evento ProgressChanged para notificar al otro hilo que los datos están listos. Otros eventos están disponibles también. THis MSDN article should get you started.

  0

la clase BackgroundWorker parece ser el único que puede enviar de forma asincrónica un NOTIFICACI en el * hilo * que creó el objeto. Internamente usa el objeto AsyncOperation llamando a asyncOperation.Post(). 23 sep. 082008-09-23 21:28:55


1

Existen muchas formas de hacerlo, dependiendo de lo que desee hacer exactamente. A producer/consumer queue es probablemente lo que quieres. Para una excelente mirada profunda a los hilos, vea el capítulo en Threading (disponible en línea) del excelente libro C# 3.0 in a Nutshell.

  0

En el modelo de productor/consumidor, el hilo principal crea unidades de trabajo que deben realizarse. Un hilo de fondo se sienta esperando a que el trabajo se ponga en cola. Cuando se crea una unidad de trabajo y se establece un evento. Eso no funciona aquí ya que el hilo principal es el "consumidor", y no puede sentarse esperando el trabajo. 23 sep. 082008-09-23 19:18:57


1

Puede usar un AutoResetEvent (o ManualResetEvent). Si usa AutoResetEvent.WaitOne (0, false), no se bloqueará.Por ejemplo:

AutoResetEvent ev = new AutoResetEvent(false); 
... 
if(ev.WaitOne(0, false)) { 
    // event happened 
} 
else { 
// do other stuff 
} 
  0

¿Cómo se verifica si se señala el evento? es decir, ¿cuándo? 21 nov. 082008-11-21 18:22:21


0

Si el hilo es el suministro de mensajes de Windows (GUI) del hilo "principal", entonces usted puede sondear utilizando un Forms.Timer - ajustar el intervalo del temporizador de acuerdo a la rapidez con que necesita tener su interfaz gráfica de usuario hilo 'observe' los datos del hilo de trabajo.

Recordar para sincronizar el acceso a la List<> compartida si se va a utilizar foreach, para evitar CollectionModified excepciones.

Utilizo esta técnica para todas las actualizaciones GUI basadas en datos de mercado en una aplicación comercial en tiempo real, y funciona muy bien.


3

Estoy combinando algunas respuestas aquí.

La situación ideal utiliza un indicador de seguridad de subprocesos como AutoResetEvent. No tiene que bloquear indefinidamente cuando llama al WaitOne(), de hecho tiene una sobrecarga que le permite especificar un tiempo de espera. Esta sobrecarga devuelve false si la bandera no se configuró durante el intervalo.

A Queue es una estructura más ideal para una relación productor/consumidor, pero puede imitarla si sus requisitos lo obligan a usar un List. La principal diferencia es que tendrá que asegurarse de que su consumidor bloquee el acceso a la colección mientras extrae los artículos; lo más seguro es usar probablemente el método CopyTo para copiar todos los elementos a una matriz y luego liberar el bloqueo. Por supuesto, asegúrese de que su productor no intente actualizar el List mientras se mantiene el bloqueo.

Aquí hay una aplicación simple de la consola C# que demuestra cómo esto podría implementarse. Si juegas con los intervalos de tiempo puedes causar que sucedan varias cosas; en esta configuración particular, estaba tratando de que el productor genere varios artículos antes de que el consumidor verifique los artículos.

using System; 
using System.Collections.Generic; 
using System.Threading; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     private static object LockObject = new Object(); 

     private static AutoResetEvent _flag; 
     private static Queue<int> _list; 

     static void Main(string[] args) 
     { 
      _list = new Queue<int>(); 
      _flag = new AutoResetEvent(false); 

      ThreadPool.QueueUserWorkItem(ProducerThread); 

      int itemCount = 0; 

      while (itemCount < 10) 
      { 
       if (_flag.WaitOne(0)) 
       { 
        // there was an item 
        lock (LockObject) 
        { 
         Console.WriteLine("Items in queue:"); 
         while (_list.Count > 0) 
         { 
          Console.WriteLine("Found item {0}.", _list.Dequeue()); 
          itemCount++; 
         } 
        } 
       } 
       else 
       { 
        Console.WriteLine("No items in queue."); 
        Thread.Sleep(125); 
       } 
      } 
     } 

     private static void ProducerThread(object state) 
     { 
      Random rng = new Random(); 

      Thread.Sleep(250); 

      for (int i = 0; i < 10; i++) 
      { 
       lock (LockObject) 
       { 
        _list.Enqueue(rng.Next(0, 100)); 
        _flag.Set(); 
        Thread.Sleep(rng.Next(0, 250)); 
       } 
      } 
     } 
    } 
} 

Si no quiere bloquear al productor, es un poco más complicado. En este caso, sugiero que el productor tenga su propia clase con un buffer público y privado y un público AutoResetEvent. El productor almacenará de manera predeterminada los elementos en el búfer privado, luego intentará escribirlos en el búfer público. Cuando el consumidor está trabajando con el búfer público, restablece el indicador en el objeto productor. Antes de que el productor intente mover elementos desde el búfer privado al búfer público, verifica este indicador y solo copia elementos cuando el consumidor no está trabajando en él.

  0

Queue parece ser una clase interesante de usar. Aunque no lo quisiera en mi situación, ya que en algún momento el usuario puede manejar toda la Lista de una vez, en lugar de tener que Decaducir elementos individuales. 23 sep. 082008-09-23 21:31:23


1

La clase BackgroundWorker es la respuesta en este caso. Es la única construcción de subprocesos que puede enviar mensajes de forma asíncrona al subproceso que creó el objeto BackgroundWorker. Internamente, BackgroundWorker utiliza la clase AsyncOperation llamando al método asyncOperation.Post().

this.asyncOperation = AsyncOperationManager.CreateOperation(null); 
this.asyncOperation.Post(delegateMethod, arg); 

Algunas otras clases en el marco .NET también utilizan AsyncOperation:

  • BackgroundWorker
  • SoundPlayer.LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DownloadFile()
  • WebClient.DownloadFileAsync()
  • cliente Web ...
  • PictureBox.LoadAsync()