Daher muss meine App eine Aktion fast ununterbrochen ausführen (mit einer Pause von ungefähr 10 Sekunden zwischen jedem Lauf), solange die App ausgeführt wird oder eine Stornierung angefordert wird. Die zu erledigende Arbeit kann bis zu 30 Sekunden dauern.
Ist es besser, einen System.Timers.Timer zu verwenden und AutoReset zu verwenden, um sicherzustellen, dass die Aktion nicht ausgeführt wird, bevor das vorherige "Häkchen" abgeschlossen ist.
Oder sollte ich eine allgemeine Aufgabe im LongRunning-Modus mit einem Stornierungs-Token verwenden und eine reguläre Endlos-while-Schleife darin haben, die die Aktion aufruft, die die Arbeit mit einem 10-Sekunden-Thread ausführt. Zwischen den Aufrufen schlafen? Was das asynchrone / wartende Modell betrifft, bin ich mir nicht sicher, ob es hier angemessen wäre, da ich keine Rückgabewerte von der Arbeit habe.
CancellationTokenSource wtoken;
Task task;
void StopWork()
{
wtoken.Cancel();
try
{
task.Wait();
} catch(AggregateException) { }
}
void StartWork()
{
wtoken = new CancellationTokenSource();
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
}
void DoWork()
{
// Some work that takes up to 30 seconds but isn't returning anything.
}
oder verwenden Sie einfach einen einfachen Timer, während Sie die AutoReset-Eigenschaft verwenden, und rufen Sie .Stop () auf, um ihn abzubrechen?
Antworten:
Ich würde dafür TPL Dataflow verwenden (da Sie .NET 4.5 verwenden und es
Task
intern verwendet). Sie können ganz einfach einActionBlock<TInput>
Element erstellen, das Elemente an sich selbst sendet, nachdem die Aktion verarbeitet und eine angemessene Zeit gewartet wurde.Erstellen Sie zunächst eine Fabrik, die Ihre unendliche Aufgabe erstellt:
Ich habe mich für
ActionBlock<TInput>
eineDateTimeOffset
Struktur entschieden . Sie müssen einen Typparameter übergeben, der auch einen nützlichen Status übergeben kann (Sie können die Art des Status ändern, wenn Sie möchten).Beachten Sie außerdem, dass
ActionBlock<TInput>
standardmäßig jeweils nur ein Element verarbeitet wird, sodass garantiert ist, dass nur eine Aktion verarbeitet wird (dh, Sie müssen sich nicht mit der Wiedereintrittsrate befassen, wenn diePost
Erweiterungsmethode auf sich selbst zurückgerufen wird).Ich habe die
CancellationToken
Struktur auch sowohl an den Konstruktor desActionBlock<TInput>
als auch an denTask.Delay
Methodenaufruf übergeben . Wenn der Vorgang abgebrochen wird, erfolgt die Stornierung bei der erstmöglichen Gelegenheit.Von dort aus ist es ein einfaches Refactoring Ihres Codes, um die von implementierte
ITargetBlock<DateTimeoffset>
Schnittstelle zu speichernActionBlock<TInput>
(dies ist die übergeordnete Abstraktion, die Blöcke darstellt, die Verbraucher sind, und Sie möchten den Verbrauch durch einen Aufruf derPost
Erweiterungsmethode auslösen können ):Ihre
StartWork
Methode:Und dann deine
StopWork
Methode:Warum sollten Sie hier TPL Dataflow verwenden? Einige Gründe:
Trennung von Bedenken
Die
CreateNeverEndingTask
Methode ist jetzt eine Fabrik, die sozusagen Ihren "Service" erstellt. Sie steuern, wann es startet und stoppt, und es ist vollständig in sich geschlossen. Sie müssen die Statussteuerung des Timers nicht mit anderen Aspekten Ihres Codes verweben. Sie erstellen einfach den Block, starten ihn und stoppen ihn, wenn Sie fertig sind.Effizientere Nutzung von Threads / Aufgaben / Ressourcen
Der Standardplaner für die Blöcke im TPL-Datenfluss ist der gleiche für a
Task
, bei dem es sich um den Thread-Pool handelt. Indem Sie dasActionBlock<TInput>
zum Verarbeiten Ihrer Aktion sowie einen Aufruf von verwendenTask.Delay
, erhalten Sie die Kontrolle über den Thread, den Sie verwendet haben, als Sie tatsächlich nichts getan haben. Zugegeben, dies führt tatsächlich zu einem gewissen Overhead, wenn Sie das neueTask
System erstellen, das die Fortsetzung verarbeitet. Dies sollte jedoch gering sein, da Sie dies nicht in einer engen Schleife verarbeiten (Sie warten zehn Sekunden zwischen den Aufrufen).Wenn die
DoWork
Funktion tatsächlich abwartbar gemacht werden kann (nämlich indem sie a zurückgibtTask
), können Sie dies (möglicherweise) noch weiter optimieren, indem Sie die oben beschriebene Factory-Methode so anpassen, dass aFunc<DateTimeOffset, CancellationToken, Task>
anstelle von a verwendet wirdAction<DateTimeOffset>
, wie folgt:Natürlich wäre es eine gute Praxis, die
CancellationToken
Methode durchzuarbeiten (falls sie eine akzeptiert), was hier durchgeführt wird.Das heißt, Sie hätten dann eine
DoWorkAsync
Methode mit der folgenden Signatur:Sie müssten die
StartWork
Methode ändern (nur geringfügig, und Sie bluten hier nicht die Trennung von Bedenken aus) , um die neue Signatur zu berücksichtigen, die an dieCreateNeverEndingTask
Methode übergeben wird, wie folgt:quelle
Ich finde die neue aufgabenbasierte Oberfläche sehr einfach, um solche Dinge zu tun - sogar einfacher als die Verwendung der Timer-Klasse.
Es gibt einige kleine Anpassungen, die Sie an Ihrem Beispiel vornehmen können. Anstatt:
Du kannst das:
Auf diese Weise erfolgt die Stornierung sofort im Inneren
Task.Delay
, anstatt warten zu müssen,Thread.Sleep
bis der Vorgang abgeschlossen ist.Wenn Sie
Task.Delay
over verwenden,Thread.Sleep
bedeutet dies auch, dass Sie keinen Thread binden, der für die Dauer des Schlafes nichts tut.Wenn Sie in der Lage sind, können Sie auch
DoWork()
ein Stornierungs-Token akzeptieren, und die Stornierung reagiert viel schneller.quelle
Task.Run
der Thread-Pool verwendet, sodass Ihr Beispiel mitTask.Run
stattTask.Factory.StartNew
mitTaskCreationOptions.LongRunning
nicht genau dasselbe tut - Wenn ich die Aufgabe zur Verwendung derLongRunning
Option benötigen würde, könnte ich sie dann nicht so verwenden,Task.Run
wie Sie es gezeigt haben, oder fehlt mir etwas?LongRunning
ist also irgendwie nicht kompatibel mit dem Ziel, keine Threads zu binden. Wenn Sie sicherstellen möchten, dass ein eigener Thread ausgeführt wird, können Sie ihn verwenden. Hier starten Sie jedoch einen Thread, der die meiste Zeit im Ruhezustand ist. Was ist der Anwendungsfall?LongRunning
Verwendung derTask.Run
Syntax angeben können . Aus der Dokumentation geht hervor, dass dieTask.Run
Syntax sauberer ist, solange Sie mit den verwendeten Standardeinstellungen zufrieden sind. Es scheint keine Überlastung zu geben, die einTaskCreationOptions
Argument erfordert .Folgendes habe ich mir ausgedacht:
NeverEndingTask
dieExecutionCore
Methode und überschreiben Sie sie mit der Arbeit, die Sie ausführen möchten.ExecutionLoopDelayMs
können Sie die Zeit zwischen Schleifen anpassen, z. B. wenn Sie einen Backoff-Algorithmus verwenden möchten.Start/Stop
Bereitstellung einer synchronen Schnittstelle zum Starten / Stoppen der Aufgabe.LongRunning
bedeutet, dass Sie einen dedizierten Thread pro erhaltenNeverEndingTask
.ActionBlock
obigen basierten Lösung keinen Speicher in einer Schleife zu .::
quelle