2

I want to cancel an async function on reentrancy, so that the work do not get stacked up and unneeded work is prevented. e.g. my file scanning can take up to 8 seconds, but when I change the folder in the UI, the old function should be canceled. I saw samples with the CancellationToken, but it seems to me like too much code.

My approach is like this, and it seems to work, but it adds to much clutter to the code. Perhaps I´m also missing to catch the TaskCanceledException which would add more code.

private CancellationTokenSource scanFilesState;
private IList<FileInfo> files;

private async Task ScanFilesAsync(string path)
{
    // stop old running
    this.scanFilesState?.Cancel();
    this.scanFilesState?.Dispose();

    this.scanFilesState = new CancellationTokenSource();

    this.files = await FileHandler.ScanFilesAsync(path, this.scanFilesState.Token);

    this.scanFilesState?.Dispose();
    this.scanFilesState = null;
}

Is there a shorter or better way?

Is there a pattern to wrap this code up?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
JohnnyBravo75
  • 253
  • 1
  • 2
  • 14
  • 1
    You could consider encapsulating this messy fuctionality in a `CancelableExecution` class, having a `Task RunAsync(Func action)` public API. There is an implementation [here](https://stackoverflow.com/questions/6960520/when-to-dispose-cancellationtokensource/61681938#61681938), that has quite a lot of code because of an additional requirement for thread-safety. In your case I guess that all of your code runs on the UI thread, so the implementation could be made much simpler. – Theodor Zoulias Feb 25 '21 at 17:06
  • Ok thanks, that seem what I needed. Most probably I run on the UI thread, but not for sure. Why a comment and not real response post, so I could mark it as answer? – JohnnyBravo75 Feb 25 '21 at 17:13
  • In GUI applications the continuation after the `await` runs on the UI thread (unless you configure it explicitly to do otherwise), so you don't have to worry about the case that one thread cancels the `CancellationTokenSource` while another thread disposes it. Whatever happens inside the `FileHandler.ScanFilesAsync` it doesn't matter, regarding the handling of the CTS. So it could make sense to spend some time writing a simpler (single-threaded) implementation, that you can customize easily at will. – Theodor Zoulias Feb 25 '21 at 17:24
  • My previous comment is essentially just a link to an existing answer. It is not answer-worthy itself IMHO. :-) – Theodor Zoulias Feb 25 '21 at 17:25
  • Cancellation is a co-operative functionality, it doesn't cancel the task already initiated, as that would cause destabilization of the system. Once Task has started, then there's no stopping, you can atmost ignore it – Mrinal Kamboj Feb 26 '21 at 19:01

1 Answers1

1

I seems that I do not need the cleanup after, and came up with this approach and wrapped CancellationTokenSource to be safe in handling.

public class SafeCancellationTokenSource : IDisposable
    {
        private CancellationTokenSource state = new CancellationTokenSource();

        public CancellationTokenSource State => state;

        public CancellationToken Token => State.Token;

        public bool IsCancellationRequested => State.IsCancellationRequested;

        public void Cancel()
        {           
            this.state?.Cancel();
            this.state?.Dispose();
            this.state = new CancellationTokenSource();
        }

        public void Dispose()
        {
            this.state?.Dispose();
            this.state = null;
        }
    }

The code now looks like this

private SafeCancellationTokenSource scanFilesState =  new SafeCancellationTokenSource();
private IList<FileInfo> files;

private async Task ScanFilesAsync(string path)
{   
    this.scanFilesState.Cancel();
    this.files = await FileHandler.ScanFilesAsync(path, this.scanFilesState.Token);
}

Edit: added call to Dispose() again

JohnnyBravo75
  • 253
  • 1
  • 2
  • 14
  • Oh yeah, I removed the Dispose() call, in fear of an ObjectDisposedException, and played with list of disposables, like suggested in the thread of your link. Thanks, I´ll remove that. – JohnnyBravo75 Feb 26 '21 at 06:31
  • OK, I deleted my comment. Btw don't be afraid of an `ObjectDisposedException` when you are in full control of a `CancellationTokenSource`. A client holding just a reference to the token (the `CancellationToken`) can do nothing that could cause an `ObjectDisposedException` to be thrown. It's only your own code that you have to worry about. For example accessing the `Token` property of a disposed `CancellationTokenSource` throws. – Theodor Zoulias Feb 26 '21 at 06:44
  • You should also pay attention to this remark from [the documentation](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource#remarks): *"This type implements the `IDisposable` interface. When you have finished using an instance of the type, you should dispose of it either directly or indirectly. [...] Otherwise, the resources it is using will not be freed until the garbage collector calls the `CancellationTokenSource` object's `Finalize` method."* – Theodor Zoulias Feb 26 '21 at 07:15
  • Puh, now I´m confused, in my orignal implemention, I had the Dispose() call. According to the link in your comment, its said, that Dispose() is not needed, when Cancel() was called, except ist is a LinkedCancellationTokenSource. [https://stackoverflow.com/questions/6960520/when-to-dispose-cancellationtokensource/61681938#61681938]. So I shoud add the call to Dispose() again? – JohnnyBravo75 Feb 26 '21 at 08:32
  • I would suggest to put it back. Disposing the throw-away sources is the correct thing to do. Since you replace the reference with a new `CancellationTokenSource` immediately after that, I can't see any possibility for the old disposed source to be accessed later on, and throw exceptions. – Theodor Zoulias Feb 26 '21 at 08:38
  • Regarding [that answer](https://stackoverflow.com/questions/6960520/when-to-dispose-cancellationtokensource/61681938#61681938]), I have read it too. My current verdict is that I should trust the documentation more that something that some guy said in the internet. – Theodor Zoulias Feb 26 '21 at 08:42
  • An alternative name for the `SafeCancellationTokenSource` class would be `RechargeableCancellationTokenSource`. Because unlike the native `CancellationTokenSource`, it is reusable. – Theodor Zoulias Feb 26 '21 at 17:03