Re-entrancy pattern 5: cancel previous invocation

107 views Asked by At

I need a re-entry task, and implementing similar code to https://blogs.msdn.microsoft.com/lucian/2014/03/03/async-re-entrancy-and-the-patterns-to-deal-with-it/ (pattern 5)

but I'm wondering if the CancellationTokenSource dispose is not missing. My implementation is adding it in .ContinueWith

    private Task _fooAsyncTask;
    private CancellationTokenSource _fooAsyncCancellation;

    public async Task Button1Click()
    {
        // Assume we're being called on UI thread... if not, the two assignments must be made atomic.
        // Note: we factor out "FooHelperAsync" to avoid an await between the two assignments. 
        // without an intervening await. 
        _fooAsyncCancellation?.Cancel();
        _fooAsyncCancellation = new CancellationTokenSource();
        _fooAsyncTask = FooHelperAsync(_fooAsyncCancellation.Token);

        await _fooAsyncTask.ContinueWith(task =>
        {
            _fooAsyncCancellation.Dispose();
            _fooAsyncCancellation = null;
        });
    }

    private async Task FooHelperAsync(CancellationToken cancel)
    {
        try { if (_fooAsyncTask != null) await _fooAsyncTask; }
        catch (OperationCanceledException) { }
        cancel.ThrowIfCancellationRequested();
        await FooAsync(cancel);
    }

    private async Task FooAsync(CancellationToken cancel)
    {
        //
    }

Is that correct?

1

There are 1 answers

1
Sir Rufo On

You should change your code to

  • set the _fooAsyncCancellation to null on the UI thread (in continuation it is not)
  • guarantee, that you dispose the CTS you created for that task.

Here the modified code

Task _fooAsyncTask;
CancellationTokenSource _fooAsyncCancellation;

async void button1_Click(object sender, EventArgs e)
{
    _fooAsyncCancellation?.Cancel();
    using (var cts = new CancellationTokenSource())
    {
        _fooAsyncCancellation = cts;

        try
        {
            await FooAsyncHelper(cts.Token);
        }
        catch (OperationCanceledException) { }

        if (_fooAsyncCancellation == cts)
        {
            _fooAsyncCancellation = null;
        }
    }
}

async Task FooAsyncHelper(CancellationToken cancellationToken)
{
    try
    {
        if (_fooAsyncTask != null)
        {
            await _fooAsyncTask;
        }
    }
    catch (OperationCanceledException) { }
    cancellationToken.ThrowIfCancellationRequested();
    await FooAsync(cancellationToken);
}

async Task FooAsync(CancellationToken cancellationToken)
{
    // just some sample work to do
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(100);
        cancellationToken.ThrowIfCancellationRequested();
    }
}