SerialPort.BaseStream.ReadAsync缺少第一个字节
问题描述:
我想在.net中使用SerialPort类。SerialPort.BaseStream.ReadAsync缺少第一个字节
我选择保持我的服务异步,所以我使用SerialPort.BaseStream
上的异步方法。
在我的异步方法中,我写了一个字节[]到串口,然后开始读取,直到我没有收到任何更多的数据在毫秒,并返回该结果。
但是,问题是,我似乎错过了所有答复的第一个字节,而不是打开串口后的第一个答复。
如果我在每次响应(读取)后关闭端口,并在执行新请求(写入)之前再次打开端口,则第一个字节不会丢失。但是,如果我在关闭后过早地打开端口,通常会导致"Access to the port 'COM4' is denied."
异常。对于每个写入/读取来说,打开/关闭似乎也是非常不必要的。
这基本上就是我的方法是这样的:
private async Task<byte[]> SendRequestAsync(byte[] request)
{
// Write the request
await _serialPort.BaseStream.WriteAsync(request, 0, request.Length);
var buffer = new byte[BUFFER_SIZE];
bool receiveComplete = false;
var bytesRead = 0;
// Read from the serial port
do
{
var responseTask = _serialPort.BaseStream.ReadAsync(buffer, bytesRead, BUFFER_SIZE - bytesRead);
if (await Task.WhenAny(responseTask, Task.Delay(300)) == responseTask)
{
bytesRead += responseTask.Result;
}
else
receiveComplete = true;
} while (!receiveComplete);
var response = new byte[bytesRead];
Array.Copy(buffer, 0, response, 0, bytesRead);
return response;
}
有什么明显的错误在我这样做的方式是什么?有没有更聪明的方法来异步实现相同?
答
仅仅因为你没有观察到最后的ReadAsync()
并不意味着它被取消,它仍然在运行,这显然是通过读取下面消息的第一个字节来体现的。
你应该做的是通过使用CancellationToken
来取消最后的ReadAsync()
。请注意,超时和读取之间可能存在竞争,但我假设如果超时已过,无法在没有另外写入的情况下完成读取。
的代码应该是这样的:
var cts = new CancellationTokenSource();
do
{
var responseTask = _serialPort.BaseStream.ReadAsync(
buffer, bytesRead, BUFFER_SIZE - bytesRead, cts.Token);
if (await Task.WhenAny(responseTask, Task.Delay(300)) == responseTask)
{
bytesRead += responseTask.Result;
}
else
{
cts.Cancel();
receiveComplete = true;
}
} while (!receiveComplete);
注意,无论是原因和解决方法是我的猜测,这当然可能是我错了,大约一个或两个人。
在'WhenAny'或'WhenAll'后做'.Result'总让我感到畏缩,我知道它永远不应该有问题,但我仍然不喜欢它。我总是喜欢'bytesRead + = await responseTask',因为任务完成,所以'await'没有任何开销,但是你得到了抛出的解包异常的优点,而不是从'.Result'获得'AggregateException'如果你确实得到错误。 –
您确定300 ms超时后流中没有字节吗?你怎么知道你错过了第一个字节?这是第一个字节在某种特定方面?我问的原因是,也许前面的响应留下了一些字节,所以在第一次读取之后,每次读取实际上都包含第一个字节,但在前一个请求的剩余字节之后。您可能需要在每次请求前刷新流。 –
我知道第一个字节缺失,因为每个响应应该以ASCII字符'$'(NMEA消息)开始。我现在已经切换到使用同步API,但在后台任务中运行它,它正在工作。虽然我不喜欢这个解决方案.. – Walkingsteak