Why does my async method not wait on "serialPort = await SerialDevice.FromIdAsync()"?

I'm trying to make a class that initializes a serial connection between a raspberry and arduino. The class should also be capable of reading and writing over the established serial connection.

The code I used is from the microsoft developers website.

using Windows.Storage.Streams;
using Windows.Devices.Enumeration;
using Windows.Devices.SerialCommunication;

public async void Serial()
{
    /* Find the selector string for the serial device   */
    string aqs = SerialDevice.GetDeviceSelector("UART0");
    /* Find the serial device with our selector string  */
    var dis = await DeviceInformation.FindAllAsync(aqs);
    /* Create an serial device with our selected device */
    SerialDevice SerialPort = await SerialDevice.FromIdAsync(dis[0].Id);
    /* 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;

    /* Write a string out over serial */
    string txBuffer = "Hello Serial";
    DataWriter dataWriter = new DataWriter();
    dataWriter.WriteString(txBuffer);
    uint bytesWritten = await SerialPort.OutputStream.WriteAsync(dataWriter.DetachBuffer());

    /* Read data in from the serial port */
    const uint maxReadLength = 1024;
    DataReader dataReader = new DataReader(SerialPort.InputStream);
    uint bytesToRead = await dataReader.LoadAsync(maxReadLength);
    string rxBuffer = dataReader.ReadString(bytesToRead);
}

I added the serialcommunication capability to the Package.appxmanifest.

<Capabilities>
  <DeviceCapability Name="serialcommunication">
    <Device Id="any">
      <Function Type="name:serialPort" />
    </Device>
  </DeviceCapability>
</Capabilities>

So far everything works just fine. The Raspberry succesfully sends and receives a message from the Arduino on the other end.

Now I try to do these steps seperately. First initialize the serial connection, so the read and write functions can be used whenever they are needed in the application.

MainPage

namespace SerialUART
{
    public sealed partial class MainPage : Page
    {
        private SerialController serial = new SerialController();

        public MainPage()
        {
            this.InitializeComponent();
            serial.Write();
            serial.Read();
        }
    }
}

SerialController

using Windows.Devices.Enumeration;
using Windows.Devices.SerialCommunication;
using Windows.Storage.Streams;

namespace SerialUART
{
    class SerialController
    {
        private SerialDevice SerialPort;
        private DataWriter dataWriter;
        private DataReader dataReader;

        public SerialController()
        {
            InitSerial();
        }

        private async void InitSerial()
        {
            string aqs = SerialDevice.GetDeviceSelector("UART0");
            var dis = await DeviceInformation.FindAllAsync(aqs);
            SerialPort = await SerialDevice.FromIdAsync(dis[0].Id);    
            // The program does not wait here and executes Write()
            // after Write() the following code will be executed
            SerialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            SerialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
            SerialPort.BaudRate = 9600;
            SerialPort.Parity = SerialParity.None;
            SerialPort.StopBits = SerialStopBitCount.One;
            SerialPort.DataBits = 8;
            dataWriter = new DataWriter();
            dataReader = new DataReader(SerialPort.InputStream);
        }

        public async void Read()
        {
            /* Read data in from the serial port */
            const uint maxReadLength = 1024;
            uint bytesToRead = await dataReader.LoadAsync(maxReadLength);
            string rxBuffer = dataReader.ReadString(bytesToRead);
            Debug.WriteLine(rxBuffer);
        }

        public async void Write()
        {
            /* Write a string out over serial */
            string txBuffer = "Hello Serial";
            dataWriter.WriteString(txBuffer);
            uint bytesWritten = await SerialPort.OutputStream.WriteAsync(dataWriter.DetachBuffer());
       }
    }
}

This code does not wait for the serialPort, dataWriter and dataReader to initialise. So they never get assigned to.

Can anyone explain to me why it doesn't wait for the serial connection? And how is it supposed to be done?

All help is appreciated. Thanks in advance!

Jon Skeet
people
quotationmark

All your async methods return void - that means they start executing, and the calling code has no easy way of waiting until it's actually completed before continuing. As soon as your use an await expression on something that hasn't completed, the async method will return to the caller, having scheduled a continuation which will execute when the awaitable has completed. In your case, you're then just continuing to the next statement.

Instead, you should make your async methods return Task, and change your calling method to wait for the asynchronous method to finish before continuing. That's hard to do elegantly because in each case you're calling the async methods from constructors, which can't themselves be async. You might want to consider using async static methods instead of doing the work in the constructors, which can call the async methods, awaiting them, then call an actual constructor which doesn't do any actual work.

Before you go into any of the detailed changes though, you should really read more about what async and await do - trying to use them without understanding them is a recipe for further problems.

people

See more on this question at Stackoverflow