.NETO:¿Cómo hacer que los datos del hilo principal de la señal del hilo de fondo estén disponibles?

StackOverflow https://stackoverflow.com/questions/122882

Pregunta

¿Cuál es la técnica adecuada a tener? HiloA señal HiloB de algún evento, sin haber HiloB ¿Sentarse bloqueado esperando que suceda un evento?

Tengo un hilo en segundo plano que llenará una Lista <T> compartida.Estoy tratando de encontrar una manera de indicar de forma asincrónica al hilo "principal" que hay datos disponibles para ser recogidos.


Consideré configurar un evento con un objeto EventWaitHandle, pero no puedo tener mi hilo principal en Event.WaitOne().


Consideré tener una devolución de llamada delegada, pero a) no quiero que el hilo principal funcione en el delegado:El hilo debe volver a trabajar agregando más cosas: no quiero que espere mientras el delegado se ejecuta, y b) el delegado debe ser reunido en el hilo principal, pero no estoy ejecutando una interfaz de usuario, no tengo Control para. Invoca al delegado contra.


Consideré tener una devolución de llamada delegada que simplemente inicia un intervalo cero System.Windows.Forms.Timer (con acceso de subproceso al temporizador sincronizado).De esta manera el hilo sólo necesita estar atascado mientras llama

Timer.Enabled = true;

pero eso parece un truco.

En los viejos tiempos, mi objeto habría creado una ventana oculta y el hilo habría publicado mensajes en el HWND de esa ventana oculta.Consideré crear un control oculto, pero deduzco que no se puede invocar en un control sin identificador creado.Además, no tengo interfaz de usuario:mi objeto podría haber sido creado en un servidor web, servicio o consola, no quiero que aparezca un control gráfico, ni quiero compilar una dependencia en System.Windows.Forms.


Consideré que mi objeto expusiera una interfaz ISynchronizeInvoke, pero luego necesitaría implementar .Invoke(), y ese es mi problema.


¿Cuál es la técnica adecuada para que el subproceso A señale al subproceso B de algún evento, sin que el subproceso B permanezca bloqueado esperando que ocurra un evento?

¿Fue útil?

Solución

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

Otros consejos

Estoy combinando algunas respuestas aquí.

La situación ideal utiliza un indicador seguro para subprocesos, como un AutoResetEvent.No tienes que bloquear indefinidamente cuando llamas WaitOne(), de hecho tiene una sobrecarga que te permite especificar un tiempo de espera.Esta sobrecarga regresa false si la bandera no se estableció durante el intervalo.

A Queue Es una estructura más ideal para una relación productor/consumidor, pero puedes imitarla si tus requisitos te obligan a utilizar una List.La principal diferencia es que tendrá que asegurarse de que su consumidor bloquee el acceso a la colección mientras extrae artículos;lo más seguro es probablemente usar el CopyTo método 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.

A continuación se muestra una aplicación de consola C# sencilla que demuestra cómo se podría implementar esto.Si juegas con los intervalos de tiempo, puedes provocar que sucedan varias cosas;En esta configuración particular, estaba intentando que el productor generara varios artículos antes de que el consumidor los verifique.

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 quieres bloquear al productor en absoluto, es un poco más complicado.En este caso, sugeriría hacer que el productor sea su propia clase con un buffer público y privado y un buffer público. AutoResetEvent.De forma predeterminada, el productor almacenará elementos en el búfer privado y luego intentará escribirlos en el búfer público.Cuando el consumidor trabaja con el búfer público, restablece el indicador en el objeto productor.Antes de que el productor intente mover elementos del búfer privado al búfer público, verifica este indicador y solo copia los elementos cuando el consumidor no está trabajando en ellos.

Si usa un trabajador en segundo plano para iniciar el segundo hilo y usa el evento ProgressChanged para notificar al otro hilo que los datos están listos.Otros eventos también están disponibles. Este artículo de MSDN debería ayudarle a empezar.

Hay muchas formas de hacer esto, dependiendo exactamente de lo que quieras hacer.A cola de productores/consumidores Probablemente sea lo que quieres.Para una excelente mirada en profundidad a los hilos, consulte el capítulo sobre Enhebrado (disponible en línea) del excelente libro C# 3.0 en pocas palabras.

Puede utilizar 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
}

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

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

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

  • FondoTrabajador
  • SoundPlayer.LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DescargarArchivo()
  • WebClient.DownloadFileAsync()
  • Cliente web...
  • PictureBox.LoadAsync()

Si su hilo "principal" es el hilo de la bomba de mensajes (GUI) de Windows, entonces puede sondear utilizando Forms.Timer: ajuste el intervalo del temporizador de acuerdo con la rapidez con la que necesita que su hilo de GUI "nota" los datos del hilo de trabajo. .

Recuerda sincronizar el acceso al compartido List<> si vas a usar foreach, para evitar CollectionModified excepciones.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top