A pattern for self-cancelling and restarting task

.net, async-await, asynchronous, c#, task-parallel-library

Solution

I think the general concept is pretty good, though I recommend you not use `ContinueWith`.

I'd just write it using regular `await`, and a lot of the "am I already running" logic is not necessary:

Task pendingTask = null; // pending session
CancellationTokenSource cts = null; // CTS for pending session

// SpellcheckAsync is called by the client app on the UI thread
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 (previousCts != null)
    {
        // cancel the previous session and wait for its termination
        previousCts.Cancel();
        try { await this.pendingTask; } catch { }
    }

    newCts.Token.ThrowIfCancellationRequested();
    this.pendingTask = SpellcheckAsyncHelper(newCts.Token);
    return await this.pendingTask;
}

// the actual task logic
async Task<bool> SpellcheckAsyncHelper(CancellationToken token)
{
    // do the work (pretty much IO-bound)
    using (...)
    {
        bool doMore = true;
        while (doMore)
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(500); // placeholder to call the provider
        }
        return doMore;
    }
}

Problem

Is there a recommended established pattern for self-cancelling and restarting tasks? E.g., I'm working on the API for background spellchecker. The spellcheck session is wrapped as `Task`. Every new session should cancel the previous one and wait for its termination (to properly re-use the resources like spellcheck service provider, etc). I've come up with something like this: ``` 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; } } ``` The client app (the UI) should just be able to call `SpellcheckAsync` as many times as desired, without worrying about cancelling a pending session. The main `doMore` loop runs on the UI thread (as it involves the UI, while all spellcheck service provider calls are IO-bound). I feel a bit uncomfortable about the fact that I had to split the API into two peices, `SpellcheckAsync` and `SpellcheckAsyncHelper`, but I can't think of a better way of doing this, and it's yet to be tested.

Original source

Related problems