简体   繁体   中英

Windows IoT BackgroundTask Async/Await

I am having a play with a Raspberry Pi 3 running Windows IoT. I have wired up a DS18B20 sensor and I am able to communicate to it fine via a UWP app.

I now wanted to make this app into a BackgroundTask app. I am using this code for the OneWire coms

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;
    }
}

And finally the 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();
    }

}

The problem I have is that when I run this code it hangs on one of the lines that disposes of the SerialDevice in OneWire class.

I have read in a few places that its related to the BackgroundTask and using Async/Await

This issue is not related to the BackgroundTask. Because your code produces the same issue in non BackgroundTask(app).

Its reason looks like SerialPort is somewhat prone to deadlock .

I found there are too many callings of onewireReset method that close and reopen the SerialPort. I don´t know why should this be done but this cause the problem.

So there is a workaround: Rewrite the related part logic and make sure open the SerialPort at the start of your program and dispose it when you don't need it any more.

I'm using the same onewire code in a background task to talk to the DS18B20 sensor and was experiencing exactly the same behaviour as you.

What I found is that if I put a delay of 100 milliseconds before calling the serial port dispose method it works

await Task.Delay(100)

I've tried less than 100 milliseconds but it just kept hanging.

This stackoverflow questions first answer explains the issue with serial ports in the .Net Framework Why Thread.Sleep() before SerialPort.Open and Close?

I have just been through the exact same problem myself yesterday (program suddenly hangs when disposing the SerialDevice object in the onewireReset(...) method) and I managed to resolve it.

Solution principle: Don't constantly dispose/reaquire the serial port. Instead, aquire the port once and reconfigure it on the fly as needed (=change the baud rate). This way the hanging SerialDevice.Dispose() call is avoided completely.

Note: In order to be able to change the baud rate, you must first detatch both the DataReader and DataWriter objects from the port stream, or you will get an exception. After the change, reattach new DataReader and DataWriter objects (remember to properly dispose of the old ones).

Modified OneWire class:

    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;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM