.NET: Comment avoir les données du thread principal du signal d'arrière-plan est disponible?

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

Question

Quelle est la technique appropriée pour que ThreadA signale ThreadB à un événement, sans que ThreadB soit bloqué dans l'attente d'un événement?

J'ai un fil d’arrière-plan qui remplira une liste partagée < T > ;. J'essaie de trouver un moyen de signaler de manière asynchrone le & "; principal &"; que des données soient disponibles pour être récupérées.

J'ai envisagé de définir un événement avec un objet EventWaitHandle, mais je ne peux pas laisser mon thread principal se placer sur un Event.WaitOne ().

J'ai envisagé d'avoir un rappel de délégué, mais a) Je ne veux pas que le thread principal travaille dans le délégué: le thread doit se remettre au travail en ajoutant plus de choses - je ne le veux pas en attente pendant l'exécution du délégué, et b) le délégué doit être placé sur le fil principal, mais je n’utilise pas d’UI, je n’ai aucun contrôle sur. Invoque le délégué.

J'ai considéré avoir un rappel de délégué qui démarre simplement un intervalle nul System.Windows.Forms.Timer (avec un accès thread au minuteur synchronisé). De cette façon, le fil doit seulement être bloqué car il appelle

Timer.Enabled = true;

mais cela semble être un hack.

Autrefois, mon objet aurait créé une fenêtre cachée et le fil aurait envoyé des messages au HWND de cette fenêtre cachée. J'ai envisagé de créer un contrôle caché, mais je suppose que vous ne pouvez pas. Invoquer sur un contrôle sans poignée créée. De plus, je n'ai pas d'interface utilisateur: mon objet aurait pu être créé sur un serveur Web, un service ou une console, je ne souhaite pas qu'un contrôle graphique apparaisse - je ne veux pas non plus compiler une dépendance sur System.Windows. Formulaires.

J'ai envisagé que mon objet expose une interface ISynchronizeInvoke, mais il me faudrait alors implémenter .Invoke (), et c'est mon problème.

Quelle est la technique appropriée pour que le fil A signale un fil B d’événement sans que le fil B ne soit bloqué en attente d’un événement?

Était-ce utile?

La solution

Voici un exemple de code pour 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.");
    }

Autres conseils

Je combine quelques réponses ici.

La situation idéale utilise un indicateur thread-safe tel qu'un AutoResetEvent. Vous n'êtes pas obligé de bloquer indéfiniment lorsque vous appelez WaitOne(). En fait, il y a une surcharge qui vous permet de spécifier un délai d'expiration. Cette surcharge renvoie false si l'indicateur n'a pas été défini pendant l'intervalle.

Un Queue est une structure plus idéale pour une relation producteur / consommateur, mais vous pouvez l'imiter si vos exigences vous obligent à utiliser un List. La principale différence est que vous allez devoir vous assurer que votre consommateur verrouille l'accès à la collection pendant qu'il extrait des éléments; le plus sûr est probablement d'utiliser la méthode CopyTo pour copier tous les éléments dans un tableau, puis libérer le verrou. Bien sûr, assurez-vous que votre producteur n'essaiera pas de mettre à jour le <=> tant que le verrou est maintenu.

Voici une simple application console C # qui montre comment cela pourrait être implémenté. Si vous jouez avec les intervalles de temps, vous pouvez provoquer diverses choses. Dans cette configuration particulière, j’essayais de demander au producteur de générer plusieurs éléments avant que le consommateur ne vérifie les éléments.

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 vous ne voulez pas bloquer le producteur, c'est un peu plus compliqué. Dans ce cas, je suggérerais de faire du producteur sa propre classe avec à la fois un tampon privé et public et un public <=>. Le producteur stocke par défaut les éléments dans le tampon privé, puis tente de les écrire dans le tampon public. Lorsque le consommateur travaille avec le tampon public, il réinitialise l'indicateur sur l'objet producteur. Avant de tenter de déplacer des éléments du tampon privé vers le tampon public, le producteur vérifie cet indicateur et ne copie que les éléments lorsque le consommateur ne travaille pas dessus.

Si vous utilisez un arrière-plan pour démarrer le deuxième thread et utilisez l'événement ProgressChanged pour informer l'autre thread que les données sont prêtes. D'autres événements sont également disponibles. Cet article MSDN devrait vous aider à démarrer .

Il existe plusieurs façons de procéder, en fonction de ce que vous voulez faire. Une file d'attente producteur / consommateur est probablement ce que vous voulez. Pour une excellente analyse approfondie des discussions, voir le chapitre sur les Threading (disponible en ligne) sur le site. excellent livre C # 3.0 en bref .

Vous pouvez utiliser un AutoResetEvent (ou ManualResetEvent). Si vous utilisez AutoResetEvent.WaitOne (0, false), il ne sera pas bloqué. Par exemple:

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

La classe BackgroundWorker est answer dans ce cas. Il s'agit de la seule construction de thread pouvant envoyer des messages de manière asynchrone au thread qui a créé l'objet BackgroundWorker. En interne, BackgroundWorker utilise la classe AsyncOperation en appelant la méthode asyncOperation.Post().

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

Quelques autres classes du framework .NET utilisent également AsyncOperation:

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

Si votre "ient principal " thread est le thread Windows Message Pump (GUI), vous pouvez alors interroger à l'aide d'un formulaire. Minuteur - réglez l'intervalle du minuteur en fonction de la rapidité avec laquelle votre thread GUI doit "remarquer" les données du thread de travail.

N'oubliez pas de synchroniser l'accès au partage List<> si vous comptez utiliser foreach pour éviter les CollectionModified exceptions.

J'utilise cette technique pour toutes les mises à jour d'IHM basées sur les données du marché dans une application de trading en temps réel, et cela fonctionne très bien.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top