Search code examples
c#async-awaittaskcancellation

Run method async with cancel support


I have to call a method from an external library. I have no control over it and it blocks the UI for 5 secounds+. It is just one method. Because I really like async/await, my code looks like that:

SoundSource = await Task.Run(() => CodecFactory.Instance.GetCodec(Path));

The problem is that it can last about 30 secounds and the user wants to cancel it. My first thought: Set in the GetAwaiter().OnCompleted an AutoResetEvent and run a while which waits 500 ms for the AutoResetEvent and than checks, if a bool variable _cancelLoading is true:

            using (var waithandler = new AutoResetEvent(false))
            {
                var x = track.GetSoundSource();
                x.GetAwaiter().OnCompleted(() => { waithandler.Set(); });
                while (!x.IsCompleted)
                {
                    await Task.Run(() => waithandler.WaitOne(500));
                    if (_cancelLoading)
                    {
                        x.GetAwaiter().OnCompleted(() => x.Result.Dispose());
                        _isLoadingSoundSource = false;
                        _cancelLoading = false;
                        return;
                    }
                }
                _isLoadingSoundSource = false;
                SoundSource = x.Result;
            }

But that is not a good solution because it doesn't know if it can set _isLoadingSoundSource to true because the new method could be still running the procedure. It could be fixed with another, global AutoResetEvent, but that's really complicated and can end in easily end in a dead look.

Is there a good way to "cancel" the method/Task (i don't have to use tasks if they don't support that). I don't need to abort it, it would be nice if it can run until it ends and than just dispose the result.


Solution

  • If you cannot cancel the actual operation (GetCodec), then all you can do is ignore the results when a cancellation is requested:

    private CancellationTokenSource _cts;
    async Task SetSoundSourceAsync(string path)
    {
      if (_cts != null)
        _cts.Cancel();
      _cts = new CancellationTokenSource();
      var token = _cts.Token;
      var result = await Task.Run(() => CodecFactory.Instance.GetCodec(path));
      if (token.IsCancellationRequested)
        return;
      SoundSource = result;
    }
    

    The example code above assumes it is called from a UI thread. If it's not, there would be a race condition on _cts.