Do tego używałbym TPL Dataflow (ponieważ używasz .NET 4.5 i używa wewnętrznie Task
). Możesz łatwo utworzyć ActionBlock<TInput>
, który umieszcza przedmioty po przetworzeniu działania i czeka na odpowiedni czas.
Najpierw należy utworzyć fabrykę, która stworzy swoją niekończącą zadanie:
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;
}
Wybrałam ActionBlock<TInput>
wziąć DateTimeOffset
structure; musisz przekazać parametr typu, a równie dobrze może przekazać jakiś użyteczny stan (możesz zmienić naturę stanu, jeśli chcesz).
Należy również pamiętać, że ActionBlock<TInput>
przez procesów domyślnych tylko jeden pozycji na raz, więc masz gwarancję, że tylko jedna akcja zostanie przetworzony (znaczy, nie będzie mieć do czynienia z reentrancy kiedy to nazywa Post
extension method z powrotem na siebie).
Podałem również CancellationToken
structure zarówno konstruktorowi ActionBlock<TInput>
, jak i Task.Delay
method; jeśli proces zostanie anulowany, anulowanie nastąpi przy pierwszej możliwej okazji.
Stamtąd łatwa refaktoryzacja kodu do przechowywania ITargetBlock<DateTimeoffset>
interface wdrożonego przez ActionBlock<TInput>
(jest to abstrakcja wyższego poziomu reprezentująca bloki, które są konsumentami, i chcesz być w stanie wywołać konsumpcję poprzez wywołanie do Post
metoda rozszerzenie):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Twój StartWork
metoda:
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);
}
I wtedy metoda 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;
}
Dlaczego chciałbyś używać tutaj TPL Dataflow? Kilka powodów:
Oddzielenie obawy
Sposób CreateNeverEndingTask
jest fabryka, która tworzy swoją „usługa” że tak powiem. Kontrolujesz kiedy się uruchamia i zatrzymuje, i jest całkowicie samowystarczalny. Nie musisz przeplatać kontroli stanu timera z innymi aspektami twojego kodu. Po prostu utwórz blok, uruchom go i zatrzymaj, gdy skończysz.
Bardziej efektywne wykorzystanie wątków/zadań/zasobów
Domyślny planista dla bloków w OC przepływu danych jest taka sama dla Task
, czyli puli wątków. Dzięki użyciu ActionBlock<TInput>
do przetworzenia akcji, a także połączenia z numerem Task.Delay
, uzyskujesz kontrolę nad wątkiem, którego używasz, kiedy nic nie robisz. To prawda, że prowadzi to do pewnego obciążenia, gdy odradzamy nowe Task
, które przetwarza kontynuację, ale to powinno być małe, biorąc pod uwagę, że nie przetwarzasz tego w ciasnej pętli (czekasz dziesięć sekund pomiędzy wywołaniami).
Jeśli funkcja DoWork
rzeczywiście mogą być wykonane awaitable (mianowicie w tym, że zwraca Task
), to można (ewentualnie) zoptymalizować ten jeszcze bardziej skomplikowany sposób fabryczną powyżej wziąć Func<DateTimeOffset, CancellationToken, Task>
zamiast Action<DateTimeOffset>
, jak tak:
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;
}
oczywiście, byłoby dobrą praktyką jest splot CancellationToken
przechodzenia na metody (jeśli przyjmuje jeden), który odbywa się tutaj.
Oznacza to byś wtedy mają DoWorkAsync
metodę z następującym podpisem:
Task DoWorkAsync(CancellationToken cancellationToken);
trzeba by zmienić (tylko nieznacznie, a ty nie jesteś krwawienie z separacji obawy tutaj) metodę StartWork
w celu uwzględnienia nowego podpisu przekazany do metody CreateNeverEndingTask
, tak:
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);
}
Zadanie wydaje się być przesadą biorąc pod uwagę to, co próbujesz osiągnąć. http://en.wikipedia.org/wiki/KISS_principle. Zatrzymaj stoper na początku OnTick(), sprawdź bool, aby zobaczyć, czy powinieneś robić cokolwiek na nie, pracuj, uruchom ponownie Timer, kiedy skończysz. –