Wzór dla zadania samoczynnego anulowania i ponownego uruchomienia
Czy istnieje zalecany wzorzec dla zadań samoczynnego anulowania i ponownego uruchamiania?
Np. Pracuję nad API do sprawdzania pisowni w tle. Sesja sprawdzania pisowni jest zapakowana jakoTask
. Każda nowa sesja powinna anulować poprzednią i czekać na jej zakończenie (aby ponownie prawidłowo wykorzystać zasoby, takie jak dostawca usług sprawdzania pisowni itp.).
Wymyśliłem coś takiego:
class Spellchecker
{
Task pendingTask = null; // pending session
CancellationTokenSource cts = null; // CTS for pending session
// SpellcheckAsync is called by the client app
public async Task<bool> SpellcheckAsync(CancellationToken token)
{
// SpellcheckAsync can be re-entered
var previousCts = this.cts;
var newCts = CancellationTokenSource.CreateLinkedTokenSource(token);
this.cts = newCts;
if (IsPendingSession())
{
// cancel the previous session and wait for its termination
if (!previousCts.IsCancellationRequested)
previousCts.Cancel();
// this is not expected to throw
// as the task is wrapped with ContinueWith
await this.pendingTask;
}
newCts.Token.ThrowIfCancellationRequested();
var newTask = SpellcheckAsyncHelper(newCts.Token);
this.pendingTask = newTask.ContinueWith((t) => {
this.pendingTask = null;
// we don't need to know the result here, just log the status
Debug.Print(((object)t.Exception ?? (object)t.Status).ToString());
}, TaskContinuationOptions.ExecuteSynchronously);
return await newTask;
}
// the actual task logic
async Task<bool> SpellcheckAsyncHelper(CancellationToken token)
{
// do not start a new session if the the previous one still pending
if (IsPendingSession())
throw new ApplicationException("Cancel the previous session first.");
// do the work (pretty much IO-bound)
try
{
bool doMore = true;
while (doMore)
{
token.ThrowIfCancellationRequested();
await Task.Delay(500); // placeholder to call the provider
}
return doMore;
}
finally
{
// clean-up the resources
}
}
public bool IsPendingSession()
{
return this.pendingTask != null &&
!this.pendingTask.IsCompleted &&
!this.pendingTask.IsCanceled &&
!this.pendingTask.IsFaulted;
}
}
Aplikacja kliencka (interfejs użytkownika) powinna po prostu zadzwonićSpellcheckAsync
tyle razy ile potrzeba, nie martwiąc się o anulowanie sesji oczekującej. GłównydoMore
pętla działa na wątku interfejsu użytkownika (ponieważ dotyczy interfejsu użytkownika, podczas gdy wszystkie wywołania dostawcy usług sprawdzania pisowni są powiązane z IO).
Czuję się trochę nieswojo z powodu faktu, że musiałem podzielić API na dwie części,SpellcheckAsync
iSpellcheckAsyncHelper
, ale nie mogę wymyślić lepszego sposobu na zrobienie tego, a to jeszcze nie zostało przetestowane.