DataWriter.DetachStream()引发'System.Runtime.InteropServices.COMException'

时间:2019-04-28 00:34:27

标签: c# uwp windows-10 iot

我正在为Raspberry Pi创建一个UWP程序。该程序的功能之一是从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

我似乎找到了一个相当“棘手的”奇怪的解决方案。 如果我在Arduino上使用Serial.write()将数据发送回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 个答案:

答案 0 :(得分:3)

您的问题源于以下事实:您正在使用一种即弃即用的方法来处理async方法。当您快速连续调用SendLightData()时,它不会等待上一个WriteAsync操作完成。

一旦执行到达第一个实际的await表达式(即await storeAsyncTask行),就会释放UI线程以处理另一次按钮单击。

单击此新按钮可以开始执行并覆盖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;
        }
}

这将确保两个操作永远不会同时执行。

其他解决方案可能是不将数据编写器存储为字段,而只是将其作为局部变量。当您避免调用之间的所有共享状态时,您可以放心地知道不会有覆盖导致的竞争条件。