DataWriter.DetachStream() 抛出 'System.Runtime.InteropServices.COMException'

DataWriter.DetachStream() throws 'System.Runtime.InteropServices.COMException'

我正在为 Raspberry Pi 创建一个 UWP 程序。该程序的功能之一是从 Arduino 发送和接收一些数据。

问题是当我多次尝试快速向 Arduino 发送数据时,我最终得到 System.Runtime.InteropServices.COMException The operation identifier is not valid. 来自 DataWriter.DetachStream()

快速发送数据效果很好,直到达到一定数量时,我才抛出异常。 对于 "rapid",我的意思是使用自动点击器点击按钮以每毫秒发送一次数据。

我没有尝试连续多次缓慢发送数据来重现问题,因为这可能需要很长时间(看到它需要大约 10-20 秒,传输之间有 1 毫秒的延迟。

我已经为这个问题寻找了很多小时的解决方案,但我似乎找不到任何相关的 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
    }
}

本以为Raspberry Pi可以将数据发送给Arduino,但是在快速数据传输一段时间后,抛出异常。

更新

我尝试按照下面的建议为 dataWriter 使用局部变量,但这会在快速数据传输一段时间后导致奇怪的行为。就好像它变慢了一样。值得注意的是,我再也没有得到异常。

很难解释它的行为方式,但 Debug.Write 记录了我发送的消息(工作正常)。然而,过了一会儿,它似乎 "slow down",即使我停止点击后,数据仍然每秒发送一次。到目前为止,它完全可以正常工作。所以我想知道我是否达到了某种限制?

更新 2

我似乎找到了一个相当 "hacky" 和奇怪的问题解决方案。 如果我在 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;
}

您的问题源于这样一个事实,即您使用的是使用 async 方法的即发即弃方法。当您快速连续调用 SendLightData() 时,它不会等待前一个 WriteAsync 操作完成。

一旦执行到第一个实际的 await 表达式 - 即 await storeAsyncTask 行,UI 线程将被释放以处理另一个按钮点击。

单击这个新按钮可以开始执行并覆盖 ArduinoHandler 同一实例中的 dataWriter 字段。当第一个 storeAsyncTask 完成执行时,它实际上会 datach 第二次调用的 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;
        }
}

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

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