Ich würde dafür TPL Dataflow verwenden (da Sie .NET 4.5 verwenden und es Task
intern verwendet). Sie können ganz einfach ein ActionBlock<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:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action.
action(now);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
Ich habe mich für ActionBlock<TInput>
eine DateTimeOffset
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 die Post
Erweiterungsmethode auf sich selbst zurückgerufen wird).
Ich habe die CancellationToken
Struktur auch sowohl an den Konstruktor des ActionBlock<TInput>
als auch an den Task.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 speichern ActionBlock<TInput>
(dies ist die übergeordnete Abstraktion, die Blöcke darstellt, die Verbraucher sind, und Sie möchten den Verbrauch durch einen Aufruf der Post
Erweiterungsmethode auslösen können ):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Ihre StartWork
Methode:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now);
}
Und dann deine StopWork
Methode:
void StopWork()
{
// CancellationTokenSource implements IDisposable.
using (wtoken)
{
// Cancel. This will cancel the task.
wtoken.Cancel();
}
// Set everything to null, since the references
// are on the class level and keeping them around
// is holding onto invalid state.
wtoken = null;
task = null;
}
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 das ActionBlock<TInput>
zum Verarbeiten Ihrer Aktion sowie einen Aufruf von verwenden Task.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 neue Task
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ückgibt Task
), können Sie dies (möglicherweise) noch weiter optimieren, indem Sie die oben beschriebene Factory-Methode so anpassen, dass a Func<DateTimeOffset, CancellationToken, Task>
anstelle von a verwendet wird Action<DateTimeOffset>
, wie folgt:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action. Wait on the result.
await action(now, cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Same as above.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
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:
Task DoWorkAsync(CancellationToken cancellationToken);
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 die CreateNeverEndingTask
Methode übergeben wird, wie folgt:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now, wtoken.Token);
}