Do tej pory używałem zadania LongRunning TPL do cyklicznej pracy w tle związanej z procesorem zamiast licznika czasu wątków, ponieważ:
- zadanie OC obsługuje anulowanie
- licznik czasu wątków może uruchomić inny wątek podczas zamykania programu, powodując możliwe problemy z usuniętymi zasobami
- szansa na przekroczenie: licznik wątków może uruchomić kolejny wątek, podczas gdy poprzedni jest nadal przetwarzany z powodu nieoczekiwanej długiej pracy (wiem, można temu zapobiec, zatrzymując i ponownie uruchamiając licznik czasu)
Jednak rozwiązanie TPL zawsze żąda dedykowanego wątku, który nie jest konieczny podczas oczekiwania na następną akcję (czyli przez większość czasu). Chciałbym użyć proponowanego rozwiązania Jeffa do wykonywania cyklicznej pracy związanej z procesorem w tle, ponieważ potrzebuje wątku Threadpool tylko wtedy, gdy jest do zrobienia, co jest lepsze dla skalowalności (zwłaszcza gdy okres interwału jest duży).
Aby to osiągnąć, sugerowałbym 4 adaptacje:
- Dodaj
ConfigureAwait(false)
do, Task.Delay()
aby wykonać doWork
akcję w wątku puli wątków, w przeciwnym razie doWork
zostanie wykonana na wątku wywołującym, co nie jest ideą równoległości
- Trzymaj się wzorca anulowania, zgłaszając wyjątek TaskCanceledException (nadal wymagany?)
- Przekaż CancellationToken, aby
doWork
umożliwić anulowanie zadania
- Dodaj parametr typu obiekt, aby podać informacje o stanie zadania (np. Zadanie TPL)
O punkcie 2 nie jestem pewien, czy async await nadal wymaga TaskCanceledExecption, czy jest to tylko najlepsza praktyka?
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}
Prosimy o komentarz do proponowanego rozwiązania ...
Aktualizacja 2016-8-30
Powyższe rozwiązanie nie wywołuje natychmiast, doWork()
ale zaczyna się await Task.Delay().ConfigureAwait(false)
od osiągnięcia przełączenia wątku dla doWork()
. Poniższe rozwiązanie rozwiązuje ten problem, opakowując pierwsze doWork()
wywołanie wTask.Run()
i czekając na nie.
Poniżej znajduje się ulepszona async \ await zamiennik dla Threading.Timer
który wykonuje anulowalną pracę cykliczną i jest skalowalny (w porównaniu z rozwiązaniem TPL), ponieważ nie zajmuje żadnego wątku podczas oczekiwania na następną akcję.
Zauważ, że w przeciwieństwie do timera, czas oczekiwania ( period
) jest stały, a nie czas cyklu; czas cyklu to suma czasu oczekiwania, którego czas trwania doWork()
może się zmieniać.
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
await Task.Run(() => doWork(taskState, cancellationToken), cancellationToken).ConfigureAwait(false);
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}