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.
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.
Share Improve this question edited 2 days ago komodosp asked 2 days ago komodospkomodosp 3,6464 gold badges36 silver badges68 bronze badges 8 | Show 3 more comments1 Answer
Reset to default 2I 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 fetting about the old one.
OnAdvertisementReceived
gets called? Presumably this implementation just has such sort of tardish reactivity. If client code has access to internalStartWatching
, it has access toStopWatching
too, correct? Why then not to callStopWatching
immediately upon choosing device? – Ryan Commented 2 days agoStopWatching()
- it would take a bit of restructuring to do this but I was hoping not to have to. In answer to your other question,OnAdvertisementReceived
gets called several times between the calling ofCancel()
andIsCancellationRequested
having a true value. – komodosp Commented 2 days agoIsCancellationRequested
= true on the firing following the call toCancel()
. – komodosp Commented 2 days ago