Windows IoT BackgroundTask Async/Await

Я играю с Raspberry Pi 3 под управлением Windows IoT. Я подключил датчик DS18B20 и могу нормально с ним общаться через приложение UWP.

Теперь я хотел превратить это приложение в приложение BackgroundTask. Я использую этот код для комиксов OneWire

class WireSearchResult
{
    public byte[] id = new byte[8];
    public int lastForkPoint = 0;
}
public class OneWire
{
    private SerialDevice serialPort = null;
    DataWriter dataWriteObject = null;
    DataReader dataReaderObject = null;

    public async Task<string> GetFirstSerialPort()
    {
        try
        {
            string aqs = SerialDevice.GetDeviceSelector("UART0");
            var dis = await DeviceInformation.FindAllAsync(aqs);
            if (dis.Count > 0)
            {
                var deviceInfo = dis.First();
                return deviceInfo.Id;
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Unable to get serial device: " + ex.Message);
        }

        return null;
    }

    public void shutdown()
    {
        if (serialPort != null)
        {
            serialPort.Dispose();
            serialPort = null;
        }
    }

    async Task<bool> onewireReset(string deviceId)
    {
        try
        {
            if (serialPort != null)
                serialPort.Dispose();

            serialPort = await SerialDevice.FromIdAsync(deviceId);

            // Configure serial settings
            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = 9600;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;

            dataWriteObject = new DataWriter(serialPort.OutputStream);
            dataWriteObject.WriteByte(0xF0);
            await dataWriteObject.StoreAsync();

            dataReaderObject = new DataReader(serialPort.InputStream);
            await dataReaderObject.LoadAsync(1);
            byte resp = dataReaderObject.ReadByte();
            if (resp == 0xFF)
            {
                System.Diagnostics.Debug.WriteLine("Nothing connected to UART");
                return false;
            }
            else if (resp == 0xF0)
            {
                System.Diagnostics.Debug.WriteLine("No 1-wire devices are present");
                return false;
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("Response: " + resp);
                serialPort.Dispose();
                serialPort = await SerialDevice.FromIdAsync(deviceId);

                // Configure serial settings
                serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
                serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
                serialPort.BaudRate = 115200;
                serialPort.Parity = SerialParity.None;
                serialPort.StopBits = SerialStopBitCount.One;
                serialPort.DataBits = 8;
                serialPort.Handshake = SerialHandshake.None;
                dataWriteObject = new DataWriter(serialPort.OutputStream);
                dataReaderObject = new DataReader(serialPort.InputStream);
                return true;
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            return false;
        }
    }

    public async Task onewireWriteByte(byte b)
    {
        for (byte i = 0; i < 8; i++, b = (byte)(b >> 1))
        {
            // Run through the bits in the byte, extracting the
            // LSB (bit 0) and sending it to the bus
            await onewireBit((byte)(b & 0x01));
        }
    }

    async Task<byte> onewireBit(byte b)
    {
        var bit = b > 0 ? 0xFF : 0x00;
        dataWriteObject.WriteByte((byte)bit);
        await dataWriteObject.StoreAsync();
        await dataReaderObject.LoadAsync(1);
        var data = dataReaderObject.ReadByte();
        return (byte)(data & 0xFF);
    }

    async Task<byte> onewireReadByte()
    {
        byte b = 0;
        for (byte i = 0; i < 8; i++)
        {
            // Build up byte bit by bit, LSB first
            b = (byte)((b >> 1) + 0x80 * await onewireBit(1));
        }
       // System.Diagnostics.Debug.WriteLine("onewireReadByte result: " + b);
        return b;
    }

    public async Task<double> getTemperature(string deviceId)
    {
        double tempCelsius = -200;

        if (await onewireReset(deviceId))
        {
            await onewireWriteByte(0xCC); //1-Wire SKIP ROM command (ignore device id)
            await onewireWriteByte(0x44); //DS18B20 convert T command 
                                          // (initiate single temperature conversion)
                                          // thermal data is stored in 2-byte temperature 
                                          // register in scratchpad memory

            // Wait for at least 750ms for data to be collated
            await Task.Delay(750);

            // Get the data
            await onewireReset(deviceId);
            await onewireWriteByte(0xCC); //1-Wire Skip ROM command (ignore device id)
            await onewireWriteByte(0xBE); //DS18B20 read scratchpad command
                                          // DS18B20 will transmit 9 bytes to master (us)
                                          // starting with the LSB

            byte tempLSB = await onewireReadByte(); //read lsb
            byte tempMSB = await onewireReadByte(); //read msb

            // Reset bus to stop sensor sending unwanted data
            await onewireReset(deviceId);

            // Log the Celsius temperature
            tempCelsius = ((tempMSB * 256) + tempLSB) / 16.0;
            var temp2 = ((tempMSB << 8) + tempLSB) * 0.0625; //just another way of calculating it

            System.Diagnostics.Debug.WriteLine("Temperature: " + tempCelsius + " degrees C " + temp2);
        }
        return tempCelsius;
    }
}

И наконец StartupTask

public sealed class StartupTask : IBackgroundTask
{
    private BackgroundTaskDeferral deferral;

    private OneWire onewire;
    private string deviceId = string.Empty;
    private bool inprog = false;
    private Timer timer;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        deferral = taskInstance.GetDeferral(); 

        onewire = new OneWire();
        deviceId = await onewire.GetFirstSerialPort();

        if(deviceId != null)
          await onewire.getTemperature(deviceId));

        BackgroundTaskDeferral.Complete();
    }

}

У меня проблема в том, что когда я запускаю этот код, он висит на одной из строк, которая избавляется от SerialDevice в OneWire учебный класс.

Я читал в нескольких местах, что это связано с BackgroundTask и использованием Async/Await

4 ответа

Решение

Я использую тот же код onewire в фоновой задаче, чтобы общаться с датчиком DS18B20, и у меня было точно такое же поведение, как и у вас.

Я обнаружил, что если перед вызовом метода утилизации последовательного порта установить задержку в 100 миллисекунд, это сработает.

await Task.Delay(100)

Я пробовал менее 100 миллисекунд, но он просто зависал.

Этот ответ на вопросы о стеке потока сначала объясняет проблему с последовательными портами в.Net Framework. Почему Thread.Sleep() перед SerialPort.Open и Close?

Эта проблема не связана с BackgroundTask. Потому что ваш код вызывает ту же проблему в не BackgroundTask(приложение).

Его причина выглядит так, будто SerialPort несколько склонен к тупику.

Я обнаружил, что слишком много призваний onewireReset метод, который закрывает и снова открывает SerialPort. Я не знаю, почему это должно быть сделано, но это вызывает проблему.

Таким образом, есть обходной путь: переписать логику связанной детали и убедиться, что открываем SerialPort в начале вашей программы и утилизируем его, когда он вам больше не нужен.

Вчера я только что столкнулся с той же самой проблемой (программа неожиданно зависает при удалении объекта SerialDevice в методе onewireReset(...)), и мне удалось ее решить.

Принцип решения: не выбрасывайте и не переоценивайте последовательный порт. Вместо этого приобретите порт один раз и перенастройте его по мере необходимости (= измените скорость передачи). Таким образом, зависание SerialDevice.Dispose() полностью исключается.

Примечание. Чтобы иметь возможность изменить скорость передачи данных, сначала необходимо отсоединить объекты DataReader и DataWriter от потока порта, иначе вы получите исключение. После изменения подключите новые объекты DataReader и DataWriter (не забудьте правильно утилизировать старые).

Модифицированный класс OneWire:

    public class OneWire
{
    private SerialDevice serialPort = null;
    DataWriter dataWriteObject = null;
    DataReader dataReaderObject = null;

    public void shutdown()
    {
        if (serialPort != null)
        {
            serialPort.Dispose();
            serialPort = null;
        }
    }

    private async Task ReconfigurePort(uint baudRate, string deviceId)
    {
        if (serialPort == null)
        {
            serialPort = await SerialDevice.FromIdAsync(deviceId);
            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = baudRate;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;
            dataWriteObject = new DataWriter(serialPort.OutputStream);
        }
        else
        {
            dataWriteObject.DetachStream();
            dataWriteObject.DetachBuffer();
            dataWriteObject.Dispose();

            dataReaderObject.DetachStream();
            dataReaderObject.Dispose();

            serialPort.BaudRate = baudRate;

            dataWriteObject = new DataWriter(serialPort.OutputStream);
        }
    }

    async Task<bool> onewireReset(string deviceId)
    {
        try
        {
            await ReconfigurePort(9600, deviceId);

            dataWriteObject.WriteByte(0xF0);
            await dataWriteObject.StoreAsync();

            dataReaderObject = new DataReader(serialPort.InputStream);
            await dataReaderObject.LoadAsync(1);

            byte resp = dataReaderObject.ReadByte();
            if (resp == 0xFF)
            {
                //System.Diagnostics.Debug.WriteLine("Nothing connected to UART");
                return false;
            }
            else if (resp == 0xF0)
            {
                //System.Diagnostics.Debug.WriteLine("No 1-wire devices are present");
                return false;
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("Response: " + resp);
                await ReconfigurePort(115200, deviceId);
                dataReaderObject = new DataReader(serialPort.InputStream);
                return true;
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            return false;
        }
    }

    public async Task onewireWriteByte(byte b)
    {
        for (byte i = 0; i < 8; i++, b = (byte)(b >> 1))
        {
            // Run through the bits in the byte, extracting the
            // LSB (bit 0) and sending it to the bus
            await onewireBit((byte)(b & 0x01));
        }
    }

    async Task<byte> onewireBit(byte b)
    {
        var bit = b > 0 ? 0xFF : 0x00;
        dataWriteObject.WriteByte((byte)bit);
        await dataWriteObject.StoreAsync();
        await dataReaderObject.LoadAsync(1);
        var data = dataReaderObject.ReadByte();
        return (byte)(data & 0xFF);
    }

    async Task<byte> onewireReadByte()
    {
        byte b = 0;
        for (byte i = 0; i < 8; i++)
        {
            // Build up byte bit by bit, LSB first
            b = (byte)((b >> 1) + 0x80 * await onewireBit(1));
        }
        //System.Diagnostics.Debug.WriteLine("onewireReadByte result: " + b);
        return b;
    }

    public async Task<double> getTemperature(string deviceId)
    {
        double tempCelsius = -200;

        if (await onewireReset(deviceId))
        {
            await onewireWriteByte(0xCC); //1-Wire SKIP ROM command (ignore device id)
            await onewireWriteByte(0x44); //DS18B20 convert T command 
                                          // (initiate single temperature conversion)
                                          // thermal data is stored in 2-byte temperature 
                                          // register in scratchpad memory

            // Wait for at least 750ms for data to be collated
            //await Task.Delay(250);

            // Get the data
            await onewireReset(deviceId);
            await onewireWriteByte(0xCC); //1-Wire Skip ROM command (ignore device id)
            await onewireWriteByte(0xBE); //DS18B20 read scratchpad command
                                          // DS18B20 will transmit 9 bytes to master (us)
                                          // starting with the LSB

            byte tempLSB = await onewireReadByte(); //read lsb
            byte tempMSB = await onewireReadByte(); //read msb

            // Reset bus to stop sensor sending unwanted data
            await onewireReset(deviceId);

            // Log the Celsius temperature
            tempCelsius = ((tempMSB * 256) + tempLSB) / 16.0;
            var temp2 = ((tempMSB << 8) + tempLSB) * 0.0625; //just another way of calculating it

            //System.Diagnostics.Debug.WriteLine("Temperature: " + tempCelsius + " degrees C " + temp2);
        }
        return tempCelsius;
    }
}

Я столкнулся с той же проблемой с зависанием SerialDevice.Dispose(). Ожидание Task.Delay(100), похоже, решило проблему. Однако я не сторонник слепого добавления задержек по непонятным мне причинам. После некоторого тестирования кажется, что то же самое можно сделать, запустив dispose для своей собственной задачи.

      //Dispose of the serial port object
System.Diagnostics.Debug.WriteLine("Disposing");
mDataWriter.DetachStream();
mDataWriter.Dispose();
mDataWriter = null;
mDataReader.DetachStream();
mDataReader.Dispose();
mDataReader = null;
try
{
    CancellationTokenSource DisposePortCancellationTokenSource = new CancellationTokenSource(200);
    await Task.Run(() => mSerialDevice.Dispose(), DisposePortCancellationTokenSource.Token);
}
catch(Exception ex)
{
    System.Diagnostics.Debug.WriteLine("Error disposing of serial device. Device may need to be manually unplugged and re-plugged in");
}
mSerialDevice = null;
System.Diagnostics.Debug.WriteLine("Disposed");
Другие вопросы по тегам