Esiste uno schema raccomandato per le attività di annullamento automatico e di riavvio?Un motivo per l'attività di annullamento automatico e riavvio
E.g., Sto lavorando all'API per il controllo ortografico di sfondo. La sessione di controllo ortografico è impacchettata come Task
. Ogni nuova sessione dovrebbe annullare quella precedente e attendere la sua conclusione (per riutilizzare correttamente le risorse come provider di servizi di controllo ortografico, ecc.).
mi è venuta in mente qualcosa di simile:
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;
}
}
L'applicazione client (l'interfaccia utente) dovrebbe essere solo in grado di chiamare SpellcheckAsync
tutte le volte che lo si desidera, senza preoccuparsi di cancellazione di una sessione in corso. Il ciclo principale doMore
viene eseguito sul thread dell'interfaccia utente (poiché coinvolge l'interfaccia utente, mentre tutte le chiamate del provider di servizi di controllo ortografico sono vincolate all'IO).
Mi sento un po 'a disagio per il fatto che ho dovuto dividere l'API in due parti, SpellcheckAsync
e SpellcheckAsyncHelper
, ma non riesco a pensare a un modo migliore di farlo e deve ancora essere testato.
@Stephen Cleary, ho un immenso rispetto per il tuo lavoro su tutte le cose asincrone, quindi per favore non prendertela nel modo sbagliato: sono semplicemente curioso. Sono in qualche modo sorpreso che tu non abbia riscritto la parte 'await this.pendingTask' usando un' SemaphoreSlim' o il tuo 'AsyncLock' o simile. Credi generalmente che migliorare la sicurezza del thread nelle porzioni "sincrone" dei metodi asincroni sia un'ottimizzazione prematura? –
@KirillShlenskiy: Non c'è niente di sbagliato nell'usare 'SemaphoreSlim' o simili per una restrizione una tantum. –