Użyłbym OC dataflow za to (ponieważ używasz .NET 4.5 i wykorzystuje Task
wewnę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ąć DateTimeOffset
strukturę ; 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 Post
metodę rozszerzenia z powrotem).
Przekazałem również CancellationToken
strukturę zarówno do konstruktora metody, jak ActionBlock<TInput>
i do wywołania Task.Delay
metody ; 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 Post
metody rozszerzenia):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Twoja StartWork
metoda:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
task.Post(DateTimeOffset.Now);
}
A potem twoja StopWork
metoda:
void StopWork()
{
using (wtoken)
{
wtoken.Cancel();
}
wtoken = null;
task = null;
}
Dlaczego miałbyś chcieć tutaj używać TPL Dataflow? Kilka powodów:
Rozdzielenie obaw
CreateNeverEndingTask
Metoda 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 DoWork
faktycznie 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 CancellationToken
metody (jeśli ją akceptuje), co jest zrobione tutaj.
Oznacza to, że miałbyś wtedy DoWorkAsync
metodę z następującym podpisem:
Task DoWorkAsync(CancellationToken cancellationToken);
Musiałbyś zmienić (tylko nieznacznie i nie wykrwawiasz tutaj oddzielenia obaw) StartWork
metodę rozliczania nowego podpisu przekazanego do CreateNeverEndingTask
metody, na przykład:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
task.Post(DateTimeOffset.Now, wtoken.Token);
}