2010-12-15 17 views
22

Oft in meinem Code beginne ich Bedrohungen im Grunde wie folgt aussehen wich:Best practice für eine endlose/periodische Ausführung von Code in C#

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

nur wissen, ob eine bestimmte Bedingung wahr ist oder nicht (zum Beispiel, wenn ein habe eine schlecht codierte Bibliothek ohne Ereignisse, nur boolesche Variablen und ich brauche eine "Live-Ansicht" von ihnen.

Jetzt frage ich mich, ob es eine bessere Möglichkeit gibt, diese Art von Arbeit wie eine Windows-Funktion zu erreichen, die meine Methoden alle x sec ausführen kann. Oder sollte ich ein globales Ereignis für meine App-Code, alle x Sekunden Anheben und lassen ihn so meine Methoden aufrufen:

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

und dann dieser Methode:

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

So, ich hoffe, das ist nicht wegen einer "subjektiven Frage" oder etwas geschlossen, möchte ich nur wissen, was die beste Praxis für diese Art von Arbeit ist.

Antwort

18

Es gibt nicht unbedingt einen "besten Weg", um Code für lange laufende Ereignisverarbeitung zu schreiben. Es hängt davon ab, welche Art von Anwendung Sie entwickeln.

Das erste Beispiel, das Sie zeigen, ist die idiomatische Weise, in der Sie häufig die Hauptmethode eines langlaufenden Threads geschrieben sehen würden. Während es im Allgemeinen wünschenswert ist, ein Mutex- oder Waitable-Ereignissynchronisations-Grundelement anstelle eines Aufrufs an Sleep() zu verwenden, ist es ansonsten ein typisches Muster, das zum Implementieren von Ereignisverarbeitungsschleifen verwendet wird. Der Vorteil dieses Ansatzes besteht darin, dass die spezielle Verarbeitung in einem separaten Thread ausgeführt werden kann. Dadurch kann der Hauptthread der Anwendung andere Aufgaben ausführen oder auf Benutzereingaben reagieren. Der Nachteil dieses Ansatzes besteht darin, dass möglicherweise Speicherbarrieren (z. B. Sperren) erforderlich sind, um sicherzustellen, dass freigegebene Ressourcen nicht beschädigt werden. Außerdem wird es schwieriger, die Benutzeroberfläche zu aktualisieren, da Sie solche Aufrufe im Allgemeinen an den UI-Thread zurückmelden müssen.

Der zweite Ansatz wird oft auch verwendet - insbesondere in Systemen, die bereits eine Ereignis-Laufwerk-API wie WinForms, WPF oder Silverlight haben. Die Verwendung eines Timer-Objekts oder eines Leerlaufereignisses ist die typische Methode, mit der periodische Hintergrundprüfungen durchgeführt werden können, wenn kein benutzerinitiiertes Ereignis die Verarbeitung auslöst. Der Vorteil hier ist, dass es einfach ist, Benutzeroberflächenobjekte zu interagieren und zu aktualisieren (da sie direkt von demselben Thread aus zugänglich sind), und es verringert die Notwendigkeit von Sperren und Mutexen für geschützte Daten. Ein möglicher Nachteil dieses Ansatzes besteht darin, dass die Verarbeitung, die durchgeführt werden muss, zeitaufwändig ist und dazu führen kann, dass Ihre Anwendung nicht auf Benutzereingaben reagiert.

Wenn Sie keine Anwendungen schreiben, die über eine Benutzeroberfläche verfügen (z. B. Dienste), wird das erste Formular viel häufiger verwendet.

Als Nebensache ... wenn möglich, ist es besser, ein Synchronisationsobjekt wie EventWaitHandle oder Semaphore zu verwenden, um zu signalisieren, wenn Arbeit zur Verarbeitung verfügbar ist. Damit können Sie Thread.Sleep- und/oder Timer-Objekte vermeiden. Es reduziert die durchschnittliche Wartezeit zwischen der Ausführung von Arbeit und der Ausführung von Ereignisverarbeitungscode und minimiert den Overhead bei der Verwendung von Hintergrundthreads, da sie von der Laufzeitumgebung effizienter geplant werden können und keine CPU-Zyklen verbrauchen bis es Arbeit gibt.

Es ist auch erwähnenswert, dass, wenn die Verarbeitung Sie als Reaktion auf die Kommunikation mit externen Quellen (MessageQueues, HTTP, TCP, etc) verwenden Sie Technologien wie WCF verwenden können, um das Skelett Ihres Ereignisbehandlungscodes bereitzustellen. WCF stellt Basisklassen bereit, die die Implementierung von Client- und Server-Systemen, die asynchron auf Kommunikationsereignisaktivitäten reagieren, erheblich vereinfachen.

+0

traurig, dass meine Herangehensweisen am meisten verwendet werden, ich hoffte, dass es dafür eine elegante Art gibt. Aber danke für diese ausführliche Antwort. – Tokk

+0

Aus Neugier, was ist mit diesen Ansätzen, die Sie unelegant finden? – LBushkin

+1

while (true) ist keine elegante Art und Weise imho ;-) – Tokk

0

Sie geben nicht viele Hintergrundinformationen darüber, warum Sie dies tun oder was Sie erreichen möchten, aber wenn es möglich ist, sollten Sie einen Windows-Dienst erstellen.

+0

Ich gebe nicht viele Informationen in warum, weil ich oft dieses Problem in verschiedenen Situationen habe ;-) Und ein Windows-Dienst ist schlecht, weil ich ein GUI brauche, und Ich möchte keine zusätzliche Kontrollanwendung erstellen. – Tokk

10

Übrigens, Thread.Sleep ist wahrscheinlich nie eine gute Idee.

Ein grundsätzliches Problem mit Thread.Sleep, dass die Menschen in der Regel nicht bewusst sind, ist, dass die interne Implementierung von Thread.Sleepnicht STA Nachrichten pumpt. Die beste und einfachste Alternative ist es, Thread.Sleep mit Thread.Join auf dem aktuellen Thread mit dem gewünschten Timeout zu ersetzen, wenn Sie eine bestimmte Zeit warten müssen und kein Kernel-Sync-Objekt verwenden können. Thread.Join wird sich gleich verhalten, d.h. der Thread würde die gewünschte Zeit warten, aber in der Zwischenzeit werden STA-Objekte gepumpt.

Warum ist das wichtig (einige detaillierte Erklärung folgt)?

Manchmal, ohne dass Sie es überhaupt wissen, hat einer Ihrer Threads möglicherweise ein STA COM-Objekt erstellt. (Dies passiert zum Beispiel manchmal im Hintergrund, wenn Sie Shell-APIs verwenden). Angenommen, ein Thread von Ihnen hat ein STA-COM-Objekt erstellt und befindet sich jetzt in einem Anruf an Thread.Sleep. Wenn irgendwann das COM-Objekt gelöscht werden muss (was zu einem unerwarteten Zeitpunkt vom GC geschehen kann), wird der Finalizer-Thread versuchen, den Distruztor des Objekts aufzurufen. Dieser Aufruf wird an den STA-Thread des Objekts weitergeleitet, der blockiert wird.

Jetzt haben Sie tatsächlich einen blockierten Finalizer-Thread. In diesen Situationen können Objekte nicht aus dem Speicher befreit werden, und schlimme Dinge werden folgen.

Also die Quintessenz: Thread.Sleep = schlecht. Thread.Join = angemessene Alternative.

5

Das erste Beispiel, das Sie zeigen, ist eine ziemlich unelegante Art, einen periodischen Timer zu implementieren. .NET hat eine Reihe von Timer-Objekten, die solche Dinge fast trivial machen. Schauen Sie in System.Windows.Forms.Timer, System.Timers.Timer und System.Threading.Timer.

Zum Beispiel hier, wie Sie eine System.Threading.Timer verwenden würde Ihr erstes Beispiel zu ersetzen:

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

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

Dieser Code CheckCondition alle 100 Millisekunden aufrufen wird (oder so).

+0

Richtig, aber das ist im Grunde mein zweites Beispiel (mit einem Timer anstelle eines Ereignisses) – Tokk

11

Wenn Sie einen Blick auf Reactive Extensions werfen, bietet es eine elegante Möglichkeit, dies unter Verwendung der observable pattern zu tun.

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

Eine nette Sache über Observablen ist die Fähigkeit, weiter Observablen aus vorhandenen zu komponieren und zu kombinieren, und auch LINQ Ausdrücke verwenden, um neue zu schaffen.Zum Beispiel, wenn Sie einen zweiten Timer, war synchron mit dem ersten, aber nur Auslösung all 1 Sekunde haben wollen, könnte man sagen

var seconds = from tick in timer where tick % 10 == 0 select tick; 
seconds.Subscribe(tick => OnSomeOtherCondition()); 
0

ein BackgroundWoker Verwendung für zusätzliche Fäden sicher Maßnahmen:

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

für weitere Informationen besuchen Sie bitte: http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

0

Eine sehr einfache Art und Weise für nicht blockierende warten andere Aufgaben Fäden/ist:

(new ManualResetEvent(false)).WaitOne(500); //Waits 500ms