.NET: Come sono disponibili i dati del thread principale del segnale del thread in background?

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

Domanda

Qual è la tecnica corretta per avere il ThreadA segnale ThreadB di alcuni eventi, senza che ThreadB rimanga bloccato in attesa che si verifichi un evento?

Ho un thread in background che riempirà un Elenco condiviso < T > ;. sto cercando di trovare un modo per segnalare in modo asincrono il " main " thread che ci sono dati disponibili per essere raccolti.


ho pensato di impostare un evento con un oggetto EventWaitHandle, ma non posso avere il mio thread principale seduto su un Event.WaitOne ().


ho considerato di avere un callback delegato, ma a) non voglio che il thread principale funzioni nel delegato: il thread deve tornare al lavoro aggiungendo altro materiale - non lo voglio aspettare mentre il delegato viene eseguito, e b) il delegato deve essere schierato sul thread principale, ma non sto eseguendo un'interfaccia utente, non ho alcun controllo su. Invoca il delegato contro.


Ho considerato un callback delegato che avvia semplicemente un intervallo zero System.Windows.Forms.Timer (con accesso thread al timer sincronizzato). In questo modo il thread deve solo essere bloccato mentre chiama

Timer.Enabled = true;

ma sembra un hack.

Ai vecchi tempi il mio oggetto avrebbe creato una finestra nascosta e il thread avrebbe postato messaggi su HWND di quella finestra nascosta. ho considerato la creazione di un controllo nascosto, ma mi rendo conto che non è possibile. Richiamare un controllo senza handle creato. Inoltre, non ho un'interfaccia utente: il mio oggetto potrebbe essere stato creato su un server Web, un servizio o una console, non voglio che appaia un controllo grafico, né voglio compilare una dipendenza da System.Windows. forme.


ho pensato che il mio oggetto potesse esporre un'interfaccia ISynchronizeInvoke, ma poi avrei dovuto implementare .Invoke (), e questo è il mio problema.


Qual è la tecnica corretta per fare in modo che il thread A segnali un thread B di qualche evento, senza che il thread B resti bloccato in attesa che si verifichi un evento?

È stato utile?

Soluzione

Ecco un esempio di codice per la classe 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.");
    }

Altri suggerimenti

Sto combinando alcune risposte qui.

La situazione ideale utilizza un flag thread-safe come AutoResetEvent. Non è necessario bloccare indefinitamente quando si chiama WaitOne(), in realtà ha un sovraccarico che consente di specificare un timeout. Questo sovraccarico restituisce false se il flag non è stato impostato durante l'intervallo.

Una Queue è una struttura più ideale per una relazione produttore / consumatore, ma puoi imitarla se i tuoi requisiti ti costringono a usare un List. La differenza principale è che dovrai assicurarti che il tuo consumatore blocchi l'accesso alla raccolta mentre estrae gli oggetti; la cosa più sicura è probabilmente usare il metodo CopyTo per copiare tutti gli elementi in un array, quindi rilasciare il blocco. Ovviamente, assicurati che il tuo produttore non proverà ad aggiornare <=> mentre il blocco è attivo.

Ecco una semplice applicazione console C # che dimostra come questo potrebbe essere implementato. Se giochi con gli intervalli di temporizzazione puoi far accadere varie cose; in questa particolare configurazione stavo cercando di fare in modo che il produttore generasse più articoli prima che il consumatore controllasse gli articoli.

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

Se non vuoi bloccare affatto il produttore, è un po 'più complicato. In questo caso, suggerirei di fare del produttore una propria classe con un buffer sia privato che pubblico e un <=> pubblico. Per impostazione predefinita, il produttore memorizzerà gli articoli nel buffer privato, quindi tenterà di scriverli nel buffer pubblico. Quando il consumatore sta lavorando con il buffer pubblico, reimposta il flag sull'oggetto produttore. Prima che il produttore tenti di spostare gli articoli dal buffer privato al buffer pubblico, controlla questo flag e copia gli articoli solo quando il consumatore non ci sta lavorando.

Se si utilizza un lavoratore in background per avviare il secondo thread e si utilizza l'evento ProgressChanged per notificare all'altro thread che i dati sono pronti. Sono disponibili anche altri eventi. Questo articolo MSDN dovrebbe iniziare .

Esistono molti modi per farlo, a seconda di cosa esattamente vuoi fare. Una coda produttore / consumatore è probabilmente ciò che desideri. Per un eccellente approfondimento sui thread, consultare il capitolo Threading (disponibile online) dal libro eccellente C # 3.0 in breve .

È possibile utilizzare un AutoResetEvent (o ManualResetEvent). Se si utilizza AutoResetEvent.WaitOne (0, false), non si bloccherà. Ad esempio:

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

La classe BackgroundWorker è la risposta in questo caso. È l'unico costrutto di threading in grado di inviare in modo asincrono messaggi al thread che ha creato l'oggetto BackgroundWorker. Internamente BackgroundWorker utilizza la classe AsyncOperation chiamando il metodo asyncOperation.Post().

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

Alcune altre classi nel framework .NET usano anche AsyncOperation:

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

Se il tuo " main " thread è il thread della GUI (message message pump) di Windows, quindi puoi eseguire il polling usando un Forms.Timer: regola l'intervallo del timer in base alla velocità con cui devi far sì che il tuo thread della GUI 'noti' i dati dal thread di lavoro.

Ricorda di sincronizzare l'accesso al List<> condiviso se intendi utilizzare foreach, per evitare CollectionModified eccezioni.

Uso questa tecnica per tutti gli aggiornamenti della GUI basati sui dati di mercato in un'applicazione di trading in tempo reale e funziona molto bene.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top