如何在设备断开连接时取消 BLE 任务?

问题描述

我正在以 Xamarin 形式进行 BLE 实现。我想在设备断开连接时停止 BLE 任务。我使用取消令牌来停止任务,并调用一个 void 方法,该方法具有返回值,如以下代码所示:

private async Task ConnectForDevice(ScanData scanData,CancellationTokenSource token)
        {
          
            try
            {
                await _adapter.ConnectToDeviceAsync(_device);
            }
            catch (DeviceConnectionException ex)
            {
              
                
                await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
                 disconnected();
                token.Cancel();
               
            }
}
 public async void disconnected()
        {
            await Application.Current.MainPage.Navigation.PopAsync();
            return;
            
        }

token.Cancel() 应该停止任务,但不会停止执行。相反,它会执行其他异步任务。有没有办法轻松停止这些异步任务?有什么建议吗?

解决方法

您使用的取消令牌错误,您的代码没有多大意义。

首先,如果这是您要取消的任务,您应该在 CancellationToken 中使用 CancellationTokenSource 而不是 ConnectForDevice 作为参数。您在调用 CancellationTokenSource 的类中创建一个 ConnectForDevices,将取消源的令牌传递给它,从而能够通过调用 cancellationTaskSource.Cancel() 从调用方取消任务。

我不知道您正在使用的 API/Library,但我认为 * _adapter.ConnectToDeviceAsync 获得 CancellationToken 会导致过载。您传递给您 cancellationToken,取消后将取消执行。

这将引发 TaskCancelledException 类型的异常(如果在 _adapter.ConnectToDevice 中实现)。我说如果实现,因为调用 cancellationTokenSource.Cancel() 只会标记要取消的令牌,设置 IsCancellationRequested=true 但某处(通常在要取消的方法内)需要调用 token. ThrowIfCancellationRequested() 或手动检查IsCancellationRequested 的状态并抛出或取消或其他任何东西,以停止执行。

此外,我认为 ConnectToDeviceAsync 方法只会连接到设备并在之后返回或抛出异常。这样您将不会收到断开连接的信号。我还认为 DeviceConnectionException 只有在连接到设备时发生异常时才会引发,但这并不意味着断开连接,因为没有连接。

通常会有一个事件通知你这件事,但也可能没有,所以唯一的方法是轮询连接或某事。

所以你的代码变得更像这样:

private async Task ConnectForDevice(ScanData scanData,CancellationToken token)
{
    try
    {
        await _adapter.ConnectToDeviceAsync(_device,token);
    }
    catch (DeviceConnectionException ex)
    {
        await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
    }
    catch (TaskCanceledException tce)
    {
        Console.WriteLine("Scan was cancelled");
    }
    finally
    {
        await Disconnect();
    }
}

public Task Disconnect() //NEVER USE ASYNC VOID,ONLY IN EVENT HANDLERS
{
    return Application.Current.MainPage.Navigation.PopAsync();
}

更新

您对 Plugin.BLE 的理解有误。 这是一个基本示例(未用于生产,用于演示)

void Main()
{
    //Create your token source
    var tcs = new CancellationTokenSource();
    //Since we dont await execution goes on to demo cancellation
    ScanAndConnectToKnownDevice(/*{yourDeviceId}*/,tcs.Token); 
    Thread.Sleep(2000)
    tcs.Cancel(); //We cancel the process after 2 seconds.
}

async Task ScanAndConnectToKnownDevice(Guid deviceId,CancellationToken cancellationToken)
{
    IDevice device;

    //CrossBluetoothLE.Current.Adapter has a few events to handle connection changes
    //We wire some of them here
    //There is an event for when a device disconnects,which
    //will be raised when ANY device disconnects
    CrossBluetoothLE.Current.Adapter.DeviceDisconnected += OnDeviceDisconnected;
    //There is an event for when a device is discovered in scan
    CrossBluetoothLE.Current.Adapter.DeviceDiscovered += OnDeviceDiscovered;

    //Start scanning process
    try
    {
        await CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync(tcs.Token);
    }
    catch (TaskCanceledException tce)
    {
        await UserDialogs.Instance.AlertAsync("Scanning was cancelled");
    }
    catch (Exception ex)
    {
        await UserDialogs.Instance.AlertAsync("Error scanning for devices");
    }
    if (device is null)
    {
        await UserDialogs.Instance.AlertAsync("Device not found!");
    }
    else
    {
        try
        {
            //This will ONLY CONNECT to the device and then RETURN after connection
            //The token you are passing here will cancel THE CONNECTION PROCESS,//that does not mean device was disconnected,but that there were problems 
//CONNECTING to the device
            await BluetoothService.ConnectForDevice(device,tcs.Token);
        }
        catch (DeviceConnectionException ex)
        {
            await UserDialogs.Instance.AlertAsync("Error connecting to the device!");           
        }
        catch (TaskCanceledException tce)
        {
            await UserDialogs.Instance.AlertAsync("Connection process was cancelled!");
        }
    }
}

private async void OnDeviceDisconnected(object sender,DeviceEventArgs e) //Is OK to use async void here
{
    if (a.Device.ID == deviceId)
        await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
}

private void OnDeviceDiscovered(object sender,DeviceEventArgs e) //Is OK to use async void here
{
    if (a.Device.ID == deviceId)
        _device = a.Device;
}