DataWriter.DetachStream () генерирует 'System.Runtime.InteropServices.COMException' - PullRequest
0 голосов
/ 28 апреля 2019

Я создаю программу UWP для Raspberry Pi. Одной из функций программы является отправка и получение данных от Arduino.

Проблема в том, что когда я пытаюсь отправить данные в Arduino быстро и много раз, я получаю System.Runtime.InteropServices.COMException The operation identifier is not valid., исходящий из DataWriter.DetachStream().

Быстрая отправка данных работает просто отлично, пока, кажется, до определенного количества, где я получаю исключение. Под «быстрым» я подразумеваю использование автоматического кликера для нажатия кнопки для отправки данных каждую миллисекунду.

Я не пытался отправлять данные медленно, много раз подряд, чтобы воспроизвести проблему, поскольку это, вероятно, заняло бы много времени (поскольку это занимает около 10-20 секунд с задержкой в ​​1 мс между передачами.

Я слишком долго искал решение этой проблемы, но, похоже, не могу найти какие-либо связанные вопросы / решения.

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

Я бы ожидал, что Raspberry Pi отправит данные в Arduino, но через некоторое время при быстрой передаче данных возникнет исключение.

Обновление

Я попытался использовать локальную переменную для dataWriter, как предложено ниже, но через некоторое время это вызывает странное поведение при быстрой передаче данных. Как будто это замедляется. Стоит отметить, что я больше не получаю исключения.

Довольно сложно пытаться объяснить, как он себя ведет, но Debug.Write записывает сообщение, которое я отправляю (что отлично работает). Однако через некоторое время он, похоже, «замедляется», и даже после того, как я перестаю нажимать, данные отправляются раз в секунду или около того. До этого момента он работал совершенно нормально. Так что мне интересно, есть ли какой-то предел, который я бью?

Обновление 2

Кажется, я нашел довольно "странное" и странное решение проблемы. Если я использую Serial.write () на Arduino для отправки данных обратно в Raspberry Pi, похоже, это как-то решило проблему.

Если кто-нибудь знает, как это работает, мне было бы очень интересно узнать:)

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 Ответ

3 голосов
/ 28 апреля 2019

Ваша проблема возникает из-за того, что вы используете метод «забыл и забыл» работать с методом async. Когда вы вызываете SendLightData() в быстрой последовательности, он не ожидает завершения предыдущей операции WriteAsync.

Как только выполнение достигает первого фактического await выражения - строки await storeAsyncTask, поток пользовательского интерфейса освобождается для обработки другого нажатия кнопки.

Этот новый щелчок кнопки может начать выполнение и перезаписать поле dataWriter в том же экземпляре ArduinoHandler. Когда первый storeAsyncTask завершит выполнение, он на самом деле отправит dataWriter второго вызова, а не свой собственный. Это может привести к множеству различных проблем и условий гонки.

Таким образом, вы должны убедиться, что невозможно нажать кнопку до того, как фактически выполнится предыдущая операция. Вы можете использовать логический флаг для этого как простое решение.

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

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

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

...