Użyłbym OC dataflow za to (ponieważ używasz .NET 4.5 i wykorzystuje Taskwewnętrznie). Możesz łatwo utworzyć, ActionBlock<TInput>który wysyła elementy do siebie po przetworzeniu jego akcji i odczekaniu odpowiedniej ilości czasu.
Najpierw stwórz fabrykę, która stworzy Twoje niekończące się zadanie:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
ActionBlock<DateTimeOffset> block = null;
block = new ActionBlock<DateTimeOffset>(async now => {
action(now);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
ConfigureAwait(false);
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
return block;
}
Wybrałam ActionBlock<TInput>wziąć DateTimeOffsetstrukturę ; musisz przekazać parametr typu i równie dobrze może on przekazać jakiś użyteczny stan (możesz zmienić naturę stanu, jeśli chcesz).
Należy również pamiętać, że ActionBlock<TInput>domyślnie przetwarza tylko jeden element naraz, więc masz gwarancję, że zostanie przetworzona tylko jedna akcja (co oznacza, że nie będziesz musiał zajmować się ponownym wejściem, gdy wywoła Postmetodę rozszerzenia z powrotem).
Przekazałem również CancellationTokenstrukturę zarówno do konstruktora metody, jak ActionBlock<TInput>i do wywołania Task.Delaymetody ; jeśli proces zostanie anulowany, anulowanie nastąpi przy pierwszej możliwej okazji.
Stamtąd można łatwo refaktoryzować kod w celu przechowywania ITargetBlock<DateTimeoffset>interfejsu zaimplementowanego przez ActionBlock<TInput>(jest to abstrakcja wyższego poziomu reprezentująca bloki, które są konsumentami, i chcesz mieć możliwość wyzwalania zużycia przez wywołanie Postmetody rozszerzenia):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Twoja StartWorkmetoda:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
task.Post(DateTimeOffset.Now);
}
A potem twoja StopWorkmetoda:
void StopWork()
{
using (wtoken)
{
wtoken.Cancel();
}
wtoken = null;
task = null;
}
Dlaczego miałbyś chcieć tutaj używać TPL Dataflow? Kilka powodów:
Rozdzielenie obaw
CreateNeverEndingTaskMetoda jest fabryka, która tworzy swoje „usługi” że tak powiem. Kontrolujesz, kiedy się uruchamia i zatrzymuje, i jest całkowicie niezależny. Nie musisz przeplatać kontroli stanu licznika czasu z innymi aspektami kodu. Po prostu tworzysz blok, uruchamiasz go i zatrzymujesz, gdy skończysz.
Bardziej efektywne wykorzystanie wątków / zadań / zasobów
Domyślny harmonogram dla bloków w przepływie danych TPL jest taki sam dla a Task, czyli puli wątków. Używając ActionBlock<TInput>do przetwarzania swojej akcji, a także wywołania Task.Delay, dajesz kontrolę nad wątkiem, którego używałeś, gdy tak naprawdę nic nie robisz. To prawda, że faktycznie prowadzi to do pewnego narzutu, gdy odradzasz nowy Task, który przetworzy kontynuację, ale to powinno być małe, biorąc pod uwagę, że nie przetwarzasz tego w ciasnej pętli (czekasz dziesięć sekund między wywołaniami).
Jeśli DoWorkfaktycznie można uczynić funkcję oczekującą (a mianowicie, że zwraca a Task), możesz (prawdopodobnie) zoptymalizować to jeszcze bardziej, dostosowując powyższą metodę fabryki, aby przyjmowała a Func<DateTimeOffset, CancellationToken, Task>zamiast an Action<DateTimeOffset>, jak na przykład:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
ActionBlock<DateTimeOffset> block = null;
block = new ActionBlock<DateTimeOffset>(async now => {
await action(now, cancellationToken).
ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
ConfigureAwait(false);
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
return block;
}
Oczywiście dobrą praktyką byłoby przeplatanie całej CancellationTokenmetody (jeśli ją akceptuje), co jest zrobione tutaj.
Oznacza to, że miałbyś wtedy DoWorkAsyncmetodę z następującym podpisem:
Task DoWorkAsync(CancellationToken cancellationToken);
Musiałbyś zmienić (tylko nieznacznie i nie wykrwawiasz tutaj oddzielenia obaw) StartWorkmetodę rozliczania nowego podpisu przekazanego do CreateNeverEndingTaskmetody, na przykład:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
task.Post(DateTimeOffset.Now, wtoken.Token);
}