我正在为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记录了我正在发送的消息(工作正常)。但是,过了一会儿,它似乎“变慢了”,甚至在我停止单击后,数据也大约每秒发送一次。到现在为止,它的工作原理完全正常。所以我想知道我是否达到某种极限?
我似乎找到了一个相当“棘手的”奇怪的解决方案。 如果我在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;
}
答案 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;
}
}
这将确保两个操作永远不会同时执行。
其他解决方案可能是不将数据编写器存储为字段,而只是将其作为局部变量。当您避免调用之间的所有共享状态时,您可以放心地知道不会有覆盖导致的竞争条件。