Vorrei usare TPL Dataflow per questo (dato che stai usando .NET 4.5 e usa Task
internamente). È possibile creare facilmente un ActionBlock<TInput>
che pubblica gli articoli su se stesso dopo che è stato elaborato e ha atteso un intervallo di tempo appropriato.
In primo luogo, creare una fabbrica che creerà il vostro compito non finisce mai:
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;
}
ho scelto il ActionBlock<TInput>
di prendere una DateTimeOffset
structure; devi passare un parametro di tipo, e potrebbe anche passare qualche stato utile (puoi cambiare la natura dello stato, se vuoi).
Si noti inoltre che il ActionBlock<TInput>
dai processi predefiniti solo uno elemento alla volta, in modo avrete la garanzia che solo un'azione sarà elaborato (significato, non avrete a che fare con reentrancy quando si chiama il Post
extension method su se stesso).
Ho anche passato il CancellationToken
structure a entrambi il costruttore dello ActionBlock<TInput>
e alla chiamata Task.Delay
method; se la procedura è annullata, la cancellazione avverrà alla prima opportunità possibile.
Da lì, è un semplice refactoring del codice per archiviare lo ITargetBlock<DateTimeoffset>
interface implementato da ActionBlock<TInput>
(questa è l'astrazione di livello superiore che rappresenta i blocchi che sono consumatori e si desidera essere in grado di attivare il consumo tramite una chiamata al Post
metodo di estensione):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
tuo StartWork
metodo:
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);
}
E poi il tuo metodo di StopWork
:
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;
}
Perché vorresti utilizzare TPL Dataflow qui? Un paio di motivi:
separazione degli interessi
Il metodo CreateNeverEndingTask
è ora una fabbrica che crea il tuo "servizio" per così dire. Tu controlli quando inizia e si ferma, ed è completamente autonomo. Non è necessario intrecciare il controllo dello stato del timer con altri aspetti del codice. Basta creare il blocco, avviarlo e fermarlo quando hai finito.
uso più efficiente di fili/attività/risorse
Lo scheduler di default per i blocchi nel flusso di dati TPL è lo stesso per un Task
, che è il pool di thread. Usando lo ActionBlock<TInput>
per elaborare la tua azione, così come una chiamata allo Task.Delay
, stai ottenendo il controllo del thread che stavi utilizzando quando non stai facendo nulla. Certo, questo porta in effetti a un certo overhead quando si genera il nuovo Task
che elaborerà la continuazione, ma che dovrebbe essere piccolo, considerando che non lo si sta elaborando in un ciclo stretto (si stanno aspettando dieci secondi tra le invocazioni).
Se la funzione DoWork
in realtà può essere fatta awaitable (vale a dire, in quanto restituisce una Task
), allora si può (forse) ottimizzare questo ancora più modificando il metodo factory precedente per dare Func<DateTimeOffset, CancellationToken, Task>
invece di un Action<DateTimeOffset>
, come così:
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;
}
Naturalmente, sarebbe buona norma tessere la CancellationToken
attraverso il metodo (se accetta uno), che viene fatto qui.
Ciò significa che si dovrebbe quindi avere un metodo DoWorkAsync
con la seguente firma:
Task DoWorkAsync(CancellationToken cancellationToken);
dovreste cambiare (solo leggermente, e non sei sanguinamento dalla separazione degli interessi qui) il metodo StartWork
per tenere conto della nuova firma passato al metodo CreateNeverEndingTask
, in questo modo:
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);
}
Task sembra un eccessivo considerando quello che si sta cercando di raggiungere. http://en.wikipedia.org/wiki/KISS_principle. Arresta il timer all'inizio di OnTick(), controlla un bool per vedere se dovresti fare qualcosa su no, fare il lavoro, riavviare Timer quando hai finito. –