I'm creating a UWP program for Raspberry Pi. One of the functions of the program is to send and receive some data from an Arduino.

The problem is when I try sending data to the Arduino rapidly and many times, I end up with System.Runtime.InteropServices.COMException The operation identifier is not valid. originating from DataWriter.DetachStream().

Sending the data rapidly works just fine, up until a certain amount it seems, where I get the exception thrown. With "rapid", I mean using an auto clicker to click a button to send data each millisecond.

I've not tried sending data slowly many times in a row to reproduce the issue, as this would probably take a long time (seeing it takes about 10-20 seconds with 1ms delay between transmissions.

I've been searching for a solution to this problem for way too many hours, but I can't seem to find any related questions/solutions.

public sealed partial class LightControl : Page
{
    int Alpha;
    int Red;
    int Green;
    int Blue;

    // This is the handler for the button to send data
    private void LightButton_Click(object sender, RoutedEventArgs e)
    {
        if (!(sender is Button button) || button.Tag == null) return;

        string tag = button.Tag.ToString();

        Alpha = int.Parse(tag.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
        Red = int.Parse(tag.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
        Green = int.Parse(tag.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
        Blue = int.Parse(tag.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);

        SendLightData();
    }

    public async void SendLightData()
    {
        await ArduinoHandler.Current.WriteAsync(ArduinoHandler.DataEnum.LightArduino,
            ArduinoHandler.DataEnum.Light, Convert.ToByte(LightConstants.LightCommand.LightCommand),
            Convert.ToByte(Red), Convert.ToByte(Green), Convert.ToByte(Blue), Convert.ToByte(Alpha),
            WriteCancellationTokenSource.Token);
    }
}
public class ArduinoHandler
{
    // Code for singleton behaviour. Included for completeness
    #region Singleton behaviour
    private static ArduinoHandler arduinoHandler;

    private static Object singletonCreationLock = new Object();

    public static ArduinoHandler Current
    {
        get
        {
            if (arduinoHandler == null)
            {
                lock (singletonCreationLock)
                {
                    if (arduinoHandler == null)
                    {
                        CreateNewArduinoHandler();
                    }
                }
            }
            return arduinoHandler;
        }
    }

    public static void CreateNewArduinoHandler()
    {
        arduinoHandler = new ArduinoHandler();
    }
    #endregion

    private DataWriter dataWriter;
    private Object WriteCancelLock = new Object();

    public async Task WriteAsync(DataEnum receiver, DataEnum sender,
        byte commandByte1, byte dataByte1, byte dataByte2, byte dataByte3,
        byte dataByte4, CancellationToken cancellationToken)
    {
        try
        {
            dataWriter = new DataWriter(arduinos[receiver].OutputStream);
            byte[] buffer;
            Task<uint> storeAsyncTask;
            lock (WriteCancelLock)
            {
                buffer = new byte[8];
                buffer[0] = Convert.ToByte(receiver);
                buffer[1] = Convert.ToByte(sender);
                buffer[2] = commandByte1;
                buffer[3] = dataByte1;
                buffer[4] = dataByte2;
                buffer[5] = dataByte3;
                buffer[6] = dataByte4;
                buffer[7] = Convert.ToByte('\n');

                cancellationToken.ThrowIfCancellationRequested();
                dataWriter.WriteBytes(buffer);
                storeAsyncTask = dataWriter.StoreAsync().AsTask(cancellationToken);
            }
            uint bytesWritten = await storeAsyncTask;
            Debug.Write("\nSent: " + BitConverter.ToString(buffer) + "\n");                    
        }
        catch (Exception e)
        {
            Debug.Write(e.Message);
        }
        finally
        {
            dataWriter.DetachStream();  // <--- I've located the exception to originate from here, using the debugger in Visual Studio
            dataWriter.Dispose();
        }
    }

    public enum DataEnum
    {
        Light = 0x01,
        Piston = 0x02,
        PC = 0x03,
        LightArduino = 0x04
    }
}

I would expect the Raspberry Pi to send the data to the Arduino, but after a while with rapid data transmission, the exception is thrown.

Update

I tried using a local variable for the dataWriter as suggested below, but this causes strange behavior after a while with rapid data transmission. Just as if it slows down. It is worth noting that I don't get an exception anymore.

Quite hard trying to explain how it behaves, but the Debug.Write logs the message I'm sending (which works fine). However, after a while, it seems to "slow down", and even after I stop clicking, the data is being sent once every second or so. It works completely fine up until this point. So I'm wondering if there is a limit of some sort I'm hitting?

Update 2

I seem to have found a rather "hacky" and weird solution to the problem. If I use Serial.write() on the Arduino to send the data back to the Raspberry Pi, it seems to have fixed the issue somehow.

If anyone knows how this worked, I'd be very interested to know :)

const int payloadSize = 8;
byte payload[payloadSize]

int numBytes;

// Called each time serial data is available
void serialEvent()
{
  numBytes = Serial.available();
  if (numBytes == payloadSize)
  {
    for (int i = 0; i < payloadSize; i++)
    {
      payload[i] = Serial.read();
      Serial.write(payload[i]); // <--- This line fixed the issue for whatever reason
    }
  }

  checkData(); // Function to do something with the data

  for (int i = 0; i < payloadSize; i++)
  {
    payload[i] = None;
  }
  numBytes = 0;
}

1 Answers

3
Martin Zikmund On Best Solutions

Your problem originates from the fact that you are using a fire-and-forget approach of working with async method. When you call SendLightData() in quick succession, it doesn't wait for the previous WriteAsync operation to complete.

Once the execution reaches the first actual await expression - which is the await storeAsyncTask line, the UI thread is freed up to handle another button click.

This new button click can start executing and overwrite the dataWriter field in the same instance of ArduinoHandler. When the first storeAsyncTask finishes executing, it will actually datach the dataWriter of the second call, not its own. This can lead to multiple different sorts of issues and race conditions.

So you must make sure that it is not possible to click the button before the previous operation actually executes. You could use a boolean flag for that as a simple solution.

private bool _isWorking = false;
public async void SendLightData()
{
    if (!_isWorking)
    {
        try
        {
           _isWorking = true;
           await ArduinoHandler.Current.WriteAsync(ArduinoHandler.DataEnum.LightArduino,
    ArduinoHandler.DataEnum.Light, Convert.ToByte(LightConstants.LightCommand.LightCommand),
    Convert.ToByte(Red), Convert.ToByte(Green), Convert.ToByte(Blue), Convert.ToByte(Alpha),
    WriteCancellationTokenSource.Token);
        }
        finally
        {
           _isWorking = false;
        }
}

This will ensure that two operations never execute simultaneously.

Other solution could be to not store the data writer as a field and just have it as a local variable. When you avoid all shared state between the calls, you can safely know that there will be no race condition stemming from overwriting.