Search code examples
c#uwpbluetooth-lowenergycancellation-token

IsCancellationRequested taking a long time to update after Cancel()


I'm noticing some strange behaviour with CancellationTokenSource wherein when I call the Cancel() method, IsCancellationRequested takes some time to update.

I am not sure if this is the cause, but in case it's relevant, it's being used in relation to a BluetoothLEAdvertisementWatcher.

Here are some of the relevant snippets to illustrate how it's being used (the entire code structure would be much too complex).

So take the following class, AdvertisementHandler :

    internal AdvertisementHandler(ILogger logger)
    {
        _logger = logger;

        _adWatcher = new BluetoothLEAdvertisementWatcher();
        _adWatcher.Received += OnAdvertisementReceived;
        _adWatcher.Stopped += OnAdvertisementWatcherStopped;
    }

    internal void StartWatching(CancellationToken token)
    {
        _cancellationToken = token;
        _adWatcher.Start();
    }

    internal void StopWatching()
    {
        _adWatcher.Stop();
    }

    private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
    {
        
        if (_cancellationToken.IsCancellationRequested)
        {
            _logger.LogDebug($"Cancelling device search");

            StopWatching();
            return;
        }

        _logger.LogDebug($"Received an advertisement");

        // Properly handle the advertisement.

Then in a separate class I have something like...

    private void DetectNewDevices()
    {
        try
        {
            _cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(120));

            _logger.LogDebug($"Looking for new {deviceName} devices");

            _adHandler.StartWatching(_cancellationSource.Token);

            Thread.Sleep(250);

        }
        catch (Exception ex)
        {
            _logger.LogException(ex);
        }
    }

    public bool ChooseDevice(IDeviceInstance deviceInstance)
    {
        if (_cancellationSource != null)
        {
            _logger.LogDebug("Choosing A device so cancelling search.");
            _cancellationSource.Cancel();
        }

        // Do some other stuff.
    }

First, DetectNewDevices() gets called, which calls StartWatching() and passes to it the cancellation token. This causes the advertisements to start flowing in (Received event fires on each advertisement.)

Then later, when I hit the ChooseDevice() function, it goes in and calls _cancellationSource.Cancel(); (I can see it in the log). However, it can take quite a bit of time, in one case almost 2 mins, before it acknowledges _cancellationToken.IsCancellationRequested as being true.

Note that this is not simply because it took that long before that event handler was next called - I have received many advertisements in the mean time (i.e. "Received an advertisement" appears in the log) and even put a breakpoint in there and seen that IsCancellationRequested is false. Eventually it does change to true, but why not instantly after calling Cancel()?

I've tagged "bluetooth-low-energy" and "uwp" in case this is just a quirk of that framework.


Solution

  • I feel a little foolish now, not sure whether to delete this question!

    Anyway, the problem, as it turns out was, as JonSkeet suggested, something in between that wasn't included in my snippets above.

    In my UI class I had been using the same CancellationTokenSource for two different tasks. If the user started either one, a new instance was created its token used.

    So sometimes, the token was passed into the controller class, but then reinitialised in the UI class - then presumably when I cancelled, it would be cancelling the new instance, while the previous one became an orphan.

    Then, since I had put a 2-minute timeout on it, the original one would cancel itself, which I misinterpreted as a delay between my cancelling and the token receiving the cancellation.

    Working on a solution right now that requires a bit of restructuring, but the main problem was my re-instantiating the token source each time I began a new task and forgetting about the old one.