Meilleure pratique pour une exécution sans fin / périodique du code en C#

Souvent dans mon Code, je commence des menaces qui ressemblent essentiellement à ceci:

void WatchForSomething()
{
    while(true)
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
        Sleep(100);
    }
}

Juste pour savoir si une condition est vraie ou non (par exemple si une bibliothèque codée est mauvaise Sans événements, juste des variables booléennes et j'ai besoin d'une "vue en direct" d'entre elles).

Maintenant, je me demande s'il existe une meilleure façon d'accomplir ce genre de travail comme une fonction windows à accrocher qui peut exécuter mes méthodes toutes les x secondes. ou devrais-je coder un événement global pour mon application, soulevant toutes les X secondes et le laisser appeler mes méthodes comme ceci:

//Event from Windows or selfmade
TicEvent += new TicEventHandler(WatchForSomething));

Et puis cette méthode:

    void WatchForSomething()
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
    }

Donc, j'espère que ce n'est pas fermé à cause d'une "question subjective" ou quelque chose comme ça, je veux juste savoir quelle est la meilleure pratique pour ce genre de travail.

22
demandé sur Tokk 2010-12-15 18:31:40

7 réponses

Il n'y a pas nécessairement un "meilleur moyen" d'écrire du code de traitement d'événements de longue durée. Cela dépend du type d'application que vous développez.

Le premier exemple que vous montrez est la manière idiomatique dans laquelle vous verriez souvent la méthode principale d'un thread de longue durée écrit. Bien qu'il soit généralement souhaitable d'utiliser une primitive de synchronisation d'événements mutex ou waitable plutôt qu'un appel à Sleep() - c'est par ailleurs un modèle typique utilisé pour implémenter le traitement des événements boucle. L'avantage de cette approche est qu'elle permet au traitement spécialisé de s'exécuter sur un thread séparé-permettant au thread principal de votre application d'effectuer d'autres tâches ou de rester réactif à l'entrée de l'utilisateur. L'inconvénient de cette approche est qu'elle peut nécessiter l'utilisation de barrières de mémoire (comme les verrous) pour s'assurer que les ressources partagées ne sont pas endommagés. Il est également plus difficile de mettre à jour votre interface utilisateur, car vous devez généralement regrouper ces appels vers le thread de L'interface utilisateur.

La seconde l'approche est également souvent utilisée-en particulier dans les systèmes qui ont déjà une API event-drive telle que WinForms, WPF ou Silverlight. L'utilisation d'un objet timer ou d'un événement Idle est la manière typique de vérifier périodiquement les antécédents s'il n'y a aucun événement initié par l'utilisateur qui déclenche votre traitement. L'avantage ici est qu'il est facile d'interagir et de mettre à jour les objets de l'interface utilisateur (car ils sont directement accessibles à partir du même thread) et qu'il atténue le besoin de verrous et de mutex aux données protégées. Un inconvénient potentiel de cette approche est que si le traitement qui doit être effectué prend du temps, il peut rendre votre application insensible à l'entrée de l'utilisateur.

Si vous n'écrivez pas d'applications qui ont une interface utilisateur (comme les services), le premier formulaire est utilisé beaucoup plus souvent.

En aparté ... lorsque possible, il est préférable d'utiliser un objet de synchronisation comme un EventWaitHandle ou Sémaphore pour signaler quand le travail est disponible pour être traité.{[3] } cela vous permet d'éviter D'utiliser Thread.Objets de veille et / ou de minuterie. Il réduit la latence moyenne entre le moment où le travail est disponible et le moment où le code de traitement des événements est déclenché, et il minimise la surcharge de l'utilisation des threads d'arrière-plan, car ils peuvent être planifiés plus efficacement par l'environnement d'exécution et ne consommeront aucun cycle de CPU jusqu'à ce qu'il y

Il convient également de mentionner que si le traitement que vous effectuez est en réponse à communications avec des sources externes (MessageQueues, HTTP, TCP, etc.) vous pouvez utiliser des technologies telles que WCF pour fournir le squelette de votre code de gestion des événements. WCF fournit des classes de base qui facilitent considérablement la mise en œuvre des systèmes Client et serveur qui répondent de manière asynchrone à l'activité des événements de communication.

18
répondu LBushkin 2010-12-15 15:55:48

Si vous jetez un oeil à Extensions réactives, il fournit une façon élégante de le faire en utilisant le Modèle observable.

var timer = Observable.Interval(Timespan.FromMilliseconds(100));
timer.Subscribe(tick => OnSomeCondition());

Une bonne chose à propos des observables est la capacité de composer et de combiner d'autres observables à partir de ceux existants, et même d'utiliser des expressions LINQ pour en créer de nouvelles. Par exemple, si vous voulez avoir une seconde minuterie synchronisée avec la première, mais ne déclenchant que toutes les 1 secondes, vous pouvez dire

var seconds = from tick in timer where tick % 10 == 0 select tick;
seconds.Subscribe(tick => OnSomeOtherCondition());
11
répondu Mark H 2010-12-15 17:00:12

Au fait, Thread.Sleep n'est probablement jamais une bonne idée.

Un problème de base avec Thread.Sleep que les gens ne sont généralement pas conscients, est que la mise en œuvre interne de Thread.Sleep ne pompe pas les messages STA. L'alternative la meilleure et la plus simple, si vous devez attendre un temps donné et ne pouvez pas utiliser un objet de synchronisation du noyau, est de remplacer Thread.Sleep par Thread.Join sur le thread actuel, avec le délai d'attente souhaité. Thread.Join se comportera de la même manière, c'est-à-dire que le thread attendrait le temps voulu, mais en attendant STA les objets seront pompés.

Pourquoi ceci est important (quelques explications détaillées suivent)?

Parfois, sans même que vous le sachiez, l'un de vos threads peut avoir créé un objet STA COM. (Par exemple, cela se produit parfois dans les coulisses lorsque vous utilisez des API Shell). Supposons maintenant qu'un de vos threads ait créé un objet STA COM, et qu'il soit maintenant dans un appel à Thread.Sleep. Si à un moment donné L'objet COM doit être supprimé (ce qui peut arriver à un moment inattendu par le GC), alors le thread Finalizer va essayer d'appeler le distruvtor de l'objet. Cet appel sera marshalled au thread STA de l'objet, qui sera bloqué.

Maintenant, en fait, vous aurez un fil de finalisation bloqué. Dans ces situations, les objets ne peuvent pas être libérés de la mémoire, et de mauvaises choses suivront.

Donc, la ligne du bas: Thread.Sleep = mauvais. Thread.Join=solution de rechange raisonnable.

10
répondu Ran 2010-12-15 15:54:11

Le premier exemple que vous montrez est un moyen plutôt inélégant d'implémenter une minuterie périodique. . Net a un certain nombre d'objets de minuterie qui rendent ce genre de chose presque trivial. Regardez dans System.Windows.Forms.Timer, System.Timers.Timer et System.Threading.Timer.

Par exemple, voici comment vous pouvez utiliser un System.Threading.Timer pour remplacer votre premier exemple:

System.Threading.Timer MyTimer = new System.Threading.Timer(CheckCondition, null, 100, 100);

void CheckCondition(object state)
{
    if (SomeCondition())
    {
        OnSomeCondition();
    }
}

CE code appellera CheckCondition toutes les 100 millisecondes (ou à peu près).

5
répondu Jim Mischel 2010-12-15 16:25:05

Vous ne fournissez pas beaucoup d'informations sur la raison pour laquelle vous faites cela, ou ce que vous essayez d'accomplir, mais si c'est possible, vous pouvez envisager de créer un service windows.

0
répondu chris 2010-12-15 15:50:15

Utilisez un BackgroundWoker pour des mesures de sécurité de thread supplémentaires:

BackgroundWorker bw = new BackgroundWorker();


bw.WorkerSupportsCancellation = true;


bw.WorkerReportsProgress = true;
.
.
.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    for (;;)
    {
        if (worker.CancellationPending == true)
        {
           e.Cancel = true;

           break;
        }

        else
        {
            // Perform a time consuming operation and report progress.

            System.Threading.Thread.Sleep(100);
        }
    }
}

Pour plus d'informations, visitez: http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

0
répondu LEM Adane 2011-01-06 10:11:07

Un moyen très simple pour attendre non bloquant d'autres threads / tâches est:

(new ManualResetEvent(false)).WaitOne(500); //Waits 500ms
0
répondu WPFGermany 2016-09-01 10:48:28